/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2021 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.
 *
 * Stream parser for RFC8949 CBOR
 */

#include "private-lib-core.h"
#include <string.h>
#include <stdio.h>

#if defined(LWS_WITH_CBOR_FLOAT)
#include <math.h>
#endif

#define lwsl_lecp lwsl_debug

static const char * const parser_errs[] = {
	"",
	"",
	"Bad CBOR coding",
	"Unknown",
	"Parser callback errored (see earlier error)",
	"Overflow"
};

enum lecp_states {
	LECP_OPC,
	LECP_COLLECT,
	LECP_SIMPLEX8,
	LECP_COLLATE,
	LECP_ONLY_SAME
};

void
lecp_construct(struct lecp_ctx *ctx, lecp_callback cb, void *user,
	       const char * const *paths, unsigned char count_paths)
{
	uint16_t x = 0x1234;

	memset(ctx, 0, sizeof(*ctx) - sizeof(ctx->buf));

	ctx->user		= user;
	ctx->pst[0].cb		= cb;
	ctx->pst[0].paths	= paths;
	ctx->pst[0].count_paths = count_paths;
	ctx->be			= *((uint8_t *)&x) == 0x12;

	ctx->st[0].s		= LECP_OPC;

	ctx->pst[0].cb(ctx, LECPCB_CONSTRUCTED);
}

void
lecp_destruct(struct lecp_ctx *ctx)
{
	/* no allocations... just let callback know what it happening */
	if (ctx->pst[0].cb)
		ctx->pst[0].cb(ctx, LECPCB_DESTRUCTED);
}

void
lecp_change_callback(struct lecp_ctx *ctx, lecp_callback cb)
{
	ctx->pst[0].cb(ctx, LECPCB_DESTRUCTED);
	ctx->pst[0].cb = cb;
	ctx->pst[0].cb(ctx, LECPCB_CONSTRUCTED);
}


const char *
lecp_error_to_string(int e)
{
	if (e > 0)
		e = 0;
	else
		e = -e;

	if (e >= (int)LWS_ARRAY_SIZE(parser_errs))
		return "Unknown error";

	return parser_errs[e];
}

static void
ex(struct lecp_ctx *ctx, void *_start, size_t len)
{
	struct _lecp_stack *st = &ctx->st[ctx->sp];
	uint8_t *start = (uint8_t *)_start;

	st->s = LECP_COLLECT;
	st->collect_rem = (uint8_t)len;

	if (ctx->be)
		ctx->collect_tgt = start;
	else
		ctx->collect_tgt = start + len - 1;
}

static void
lecp_check_path_match(struct lecp_ctx *ctx)
{
	const char *p, *q;
	size_t s = sizeof(char *);
	int n;

	if (ctx->path_stride)
		s = ctx->path_stride;

	/* we only need to check if a match is not active */
	for (n = 0; !ctx->path_match &&
	     n < ctx->pst[ctx->pst_sp].count_paths; n++) {
		ctx->wildcount = 0;
		p = ctx->path;

		q = *((char **)(((char *)ctx->pst[ctx->pst_sp].paths) +
							((unsigned int)n * s)));

		while (*p && *q) {
			if (*q != '*') {
				if (*p != *q)
					break;
				p++;
				q++;
				continue;
			}
			ctx->wild[ctx->wildcount++] =
				    (uint16_t)lws_ptr_diff_size_t(p, ctx->path);
			q++;
			/*
			 * if * has something after it, match to .
			 * if ends with *, eat everything.
			 * This implies match sequences must be ordered like
			 *  x.*.*
			 *  x.*
			 * if both options are possible
			 */
			while (*p && (*p != '.' || !*q))
				p++;
		}
		if (*p || *q)
			continue;

		ctx->path_match = (uint8_t)(n + 1);
		ctx->path_match_len = ctx->pst[ctx->pst_sp].ppos;
		return;
	}

	if (!ctx->path_match)
		ctx->wildcount = 0;
}

int
lecp_push(struct lecp_ctx *ctx, char s_start, char s_end, char state)
{
	struct _lecp_stack *st = &ctx->st[ctx->sp];

	if (ctx->sp + 1 == LWS_ARRAY_SIZE(ctx->st))
		return LECP_STACK_OVERFLOW;

	if (s_start && ctx->pst[ctx->pst_sp].cb(ctx, s_start))
		return LECP_REJECT_CALLBACK;

	lwsl_lecp("%s: pushing from sp %d, parent "
		  "(opc %d, indet %d, collect_rem %d)\n",
		  __func__, ctx->sp, st->opcode >> 5, st->indet,
		  (int)st->collect_rem);


	st->pop_iss = s_end; /* issue this when we pop back here */
	ctx->st[ctx->sp + 1] = *st;
	ctx->sp++;
	st++;

	st->s			= state;
	st->collect_rem		= 0;
	st->intermediate	= 0;
	st->indet		= 0;
	st->ordinal		= 0;
	st->send_new_array_item = 0;
	st->barrier		= 0;

	return 0;
}

int
lecp_pop(struct lecp_ctx *ctx)
{
	struct _lecp_stack *st;

	assert(ctx->sp);
	ctx->sp--;

	st = &ctx->st[ctx->sp];

	if (st->pop_iss == LECPCB_ARRAY_END) {
		assert(ctx->ipos);
		ctx->ipos--;
	}

	ctx->pst[ctx->pst_sp].ppos = st->p;
	ctx->path[st->p] = '\0';
	lecp_check_path_match(ctx);

	lwsl_lecp("%s: popping to sp %d, parent "
		  "(opc %d, indet %d, collect_rem %d)\n",
		   __func__, ctx->sp, st->opcode >> 5, st->indet,
		   (int)st->collect_rem);

	if (st->pop_iss && ctx->pst[ctx->pst_sp].cb(ctx, st->pop_iss))
		return LECP_REJECT_CALLBACK;

	return 0;
}

static struct _lecp_stack *
lwcp_st_parent(struct lecp_ctx *ctx)
{
	assert(ctx->sp);

	return &ctx->st[ctx->sp - 1];
}

int
lwcp_completed(struct lecp_ctx *ctx, char indet)
{
	int r, il = ctx->ipos;

	ctx->st[ctx->sp].s = LECP_OPC;

	while (ctx->sp && !ctx->st[ctx->sp].barrier) {
		struct _lecp_stack *parent = lwcp_st_parent(ctx);

		lwsl_lecp("%s: sp %d, parent "
			  "(opc %d, indet %d, collect_rem %d)\n",
			  __func__, ctx->sp, parent->opcode >> 5, parent->indet,
			  (int)parent->collect_rem);

		parent->ordinal++;
		if (parent->opcode == LWS_CBOR_MAJTYP_ARRAY) {
			assert(il);
			il--;
			ctx->i[il]++;
			if (!parent->send_new_array_item) {
				if (ctx->pst[ctx->pst_sp].cb(ctx,
						LECPCB_ARRAY_ITEM_END))
					return LECP_REJECT_CALLBACK;
				parent->send_new_array_item = 1;
			}
		}

		if (!indet && parent->indet) {
			lwsl_lecp("%s: abandoning walk as parent needs indet\n", __func__);
			break;
		}

		if (!parent->indet && parent->collect_rem) {
			parent->collect_rem--;
			lwsl_lecp("%s: sp %d, parent (opc %d, indet %d, collect_rem -> %d)\n",
					__func__, ctx->sp, parent->opcode >> 5, parent->indet, (int)parent->collect_rem);

			if (parent->collect_rem) {
				/* more items to come */
				if (parent->opcode == LWS_CBOR_MAJTYP_ARRAY)
					parent->send_new_array_item = 1;
				break;
			}
		}

		lwsl_lecp("%s: parent (opc %d) collect_rem became zero\n", __func__, parent->opcode >> 5);

		ctx->st[ctx->sp - 1].s = LECP_OPC;
		r = lecp_pop(ctx);
		if (r)
			return r;
		indet = 0;
	}

	return 0;
}

static int
lwcp_is_indet_string(struct lecp_ctx *ctx)
{
	if (ctx->st[ctx->sp].indet)
		return 1;

	if (!ctx->sp)
		return 0;

	if (lwcp_st_parent(ctx)->opcode != LWS_CBOR_MAJTYP_BSTR &&
	    lwcp_st_parent(ctx)->opcode != LWS_CBOR_MAJTYP_TSTR)
		return 0;

	if (ctx->st[ctx->sp - 1].indet)
		return 1;

	return 0;
}

static int
report_raw_cbor(struct lecp_ctx *ctx)
{
	struct _lecp_parsing_stack *pst = &ctx->pst[ctx->pst_sp];

	if (!ctx->cbor_pos)
		return 0;

	ctx->cbor_len = ctx->cbor_pos;
	ctx->cbor_pos = 0;                /* reset BEFORE callback */

	if (pst->cb(ctx, LECPCB_LITERAL_CBOR))
		return 1;

	return 0;
}

void
lecp_parse_report_raw(struct lecp_ctx *ctx, int on)
{
	ctx->literal_cbor_report = (uint8_t)on;
	report_raw_cbor(ctx);
}

int
lecp_parse_map_is_key(struct lecp_ctx *ctx)
{
	return lwcp_st_parent(ctx)->opcode == LWS_CBOR_MAJTYP_MAP &&
	       !(lwcp_st_parent(ctx)->ordinal & 1);
}

int
lecp_parse_subtree(struct lecp_ctx *ctx, const uint8_t *in, size_t len)
{
	struct _lecp_stack *st = &ctx->st[++ctx->sp];
	int n;

	st->s			= 0;
	st->collect_rem		= 0;
	st->intermediate	= 0;
	st->indet		= 0;
	st->ordinal		= 0;
	st->send_new_array_item = 0;
	st->barrier		= 1;

	n = lecp_parse(ctx, in, len);
	ctx->sp--;

	return n;
}

int
lecp_parse(struct lecp_ctx *ctx, const uint8_t *cbor, size_t len)
{
	size_t olen = len;
	int ret;

	while (len--) {
		struct _lecp_parsing_stack *pst = &ctx->pst[ctx->pst_sp];
		struct _lecp_stack *st = &ctx->st[ctx->sp];
		uint8_t c, sm, o;
		char to;

		c = *cbor++;

		/*
		 * for, eg, cose_sign, we sometimes need to collect subtrees of
		 * raw CBOR.  Report buffers of it via the callback if we filled
		 * the buffer, or we stopped collecting.
		 */

		if (ctx->literal_cbor_report) {
			if (ctx->cbor_pos >= sizeof(ctx->cbor)) {
				if (report_raw_cbor(ctx))
					goto reject_callback;
			}
			ctx->cbor[ctx->cbor_pos++] = c;
		}

		switch (st->s) {
		/*
		 * We're getting the nex opcode
		 */
		case LECP_OPC:
			st->opcode = ctx->item.opcode = c & LWS_CBOR_MAJTYP_MASK;
			sm = c & LWS_CBOR_SUBMASK;
			to = 0;

			lwsl_lecp("%s: %d: OPC %d|%d\n", __func__, ctx->sp,
					c >> 5, sm);

			if (c != 0xff && ctx->sp &&
			    ctx->st[ctx->sp - 1].send_new_array_item) {
				ctx->st[ctx->sp - 1].send_new_array_item = 0;
				if (ctx->pst[ctx->pst_sp].cb(ctx,
						LECPCB_ARRAY_ITEM_START))
					goto reject_callback;
			}

			switch (st->opcode) {
			case LWS_CBOR_MAJTYP_UINT:
				ctx->present = LECPCB_VAL_NUM_UINT;
				if (sm < LWS_CBOR_1) {
					ctx->item.u.i64 = (int64_t)sm;
					goto issue;
				}
				goto i2_l;

			case LWS_CBOR_MAJTYP_INT_NEG:
				ctx->present = LECPCB_VAL_NUM_INT;
				if (sm < 24) {
					ctx->item.u.i64 = (-1ll) - (int64_t)sm;
					goto issue;
				}
i2_l:
				if (sm >= LWS_CBOR_RESERVED)
					goto bad_coding;
				ctx->item.u.u64 = 0;
				o = (uint8_t)(1 << (sm - LWS_CBOR_1));
				ex(ctx, (uint8_t *)&ctx->item.u.u64, o);
				break;

			case LWS_CBOR_MAJTYP_BSTR:
				to = LECPCB_VAL_BLOB_END - LECPCB_VAL_STR_END;

				/* fallthru */

			case LWS_CBOR_MAJTYP_TSTR:
				/*
				 * The first thing is the string length, it's
				 * going to either be a byte count for the
				 * string or the indefinite length marker
				 * followed by determinite-length chunks of the
				 * same MAJTYP
				 */

				ctx->npos = 0;
				ctx->buf[0] = '\0';

				if (!sm) {
					if ((!ctx->sp || (ctx->sp &&
					    !ctx->st[ctx->sp - 1].intermediate)) &&
					    pst->cb(ctx, (char)(LECPCB_VAL_STR_START + to)))
						goto reject_callback;

					if (pst->cb(ctx, (char)(LECPCB_VAL_STR_END + to)))
						goto reject_callback;
					lwcp_completed(ctx, 0);
					break;
				}

				if (sm < LWS_CBOR_1) {
					ctx->item.u.u64 = (uint64_t)sm;
					if ((!ctx->sp || (ctx->sp &&
					    !ctx->st[ctx->sp - 1].intermediate)) &&
					    pst->cb(ctx, (char)(LECPCB_VAL_STR_START + to)))
						goto reject_callback;

					st->indet = 0;
					st->collect_rem = sm;
					st->s = LECP_COLLATE;
					break;
				}

				if (sm < LWS_CBOR_RESERVED)
					goto i2_l;

				if (sm != LWS_CBOR_INDETERMINITE)
					goto bad_coding;

				if ((!ctx->sp || (ctx->sp &&
				    !ctx->st[ctx->sp - 1].intermediate)) &&
				    pst->cb(ctx, (char)(LECPCB_VAL_STR_START + to)))
					goto reject_callback;

				st->indet = 1;

				st->p = pst->ppos;
				lecp_push(ctx, 0, (char)(LECPCB_VAL_STR_END + to),
						  LECP_ONLY_SAME);
				break;

			case LWS_CBOR_MAJTYP_ARRAY:
				ctx->npos = 0;
				ctx->buf[0] = '\0';

				if (pst->ppos + 3u >= sizeof(ctx->path))
					goto reject_overflow;

				st->p = pst->ppos;
				ctx->path[pst->ppos++] = '[';
				ctx->path[pst->ppos++] = ']';
				ctx->path[pst->ppos] = '\0';

				lecp_check_path_match(ctx);

				if (ctx->ipos + 1u >= LWS_ARRAY_SIZE(ctx->i))
					goto reject_overflow;

#if defined(__COVERITY__)
				ctx->ipos = 0;
#endif
				ctx->i[ctx->ipos++] = 0;

				if (pst->cb(ctx, LECPCB_ARRAY_START))
					goto reject_callback;

				if (!sm) {
					if (pst->cb(ctx, LECPCB_ARRAY_END))
						goto reject_callback;
					pst->ppos = st->p;
					ctx->path[pst->ppos] = '\0';
					if (ctx->ipos) /* cov */
						ctx->ipos--;
					lecp_check_path_match(ctx);
					lwcp_completed(ctx, 0);
					break;
				}

				ctx->st[ctx->sp].send_new_array_item = 1;

				if (sm < LWS_CBOR_1) {
					st->indet = 0;
					st->collect_rem = sm;
					goto push_a;
				}

				if (sm < LWS_CBOR_RESERVED)
					goto i2_l;

				if (sm != LWS_CBOR_INDETERMINITE)
					goto bad_coding;

				st->indet = 1;
push_a:
				lecp_push(ctx, 0, LECPCB_ARRAY_END, LECP_OPC);
				break;

			case LWS_CBOR_MAJTYP_MAP:
				ctx->npos = 0;
				ctx->buf[0] = '\0';

				if (pst->ppos + 1u >= sizeof(ctx->path))
					goto reject_overflow;

				st->p = pst->ppos;
				ctx->path[pst->ppos++] = '.';
				ctx->path[pst->ppos] = '\0';

				lecp_check_path_match(ctx);

				if (pst->cb(ctx, LECPCB_OBJECT_START))
					goto reject_callback;

				if (!sm) {
					if (pst->cb(ctx, LECPCB_OBJECT_END))
						goto reject_callback;
					pst->ppos = st->p;
					ctx->path[pst->ppos] = '\0';
					lecp_check_path_match(ctx);
					lwcp_completed(ctx, 0);
					break;
				}
				if (sm < LWS_CBOR_1) {
					st->indet = 0;
					st->collect_rem = (uint64_t)(sm * 2);
					goto push_m;
				}

				if (sm < LWS_CBOR_RESERVED)
					goto i2_l;

				if (sm != LWS_CBOR_INDETERMINITE)
					goto bad_coding;

				st->indet = 1;
push_m:
				lecp_push(ctx, 0, LECPCB_OBJECT_END, LECP_OPC);
				break;

			case LWS_CBOR_MAJTYP_TAG:
				/* tag has one or another kind of int first */
				if (sm < LWS_CBOR_1) {
					/*
					 * We have a literal tag number, push
					 * to decode the tag body
					 */
					ctx->item.u.u64 = st->tag = (uint64_t)sm;
					goto start_tag_enclosure;
				}
				/*
				 * We have to do more stuff to get the tag
				 * number...
				 */
				goto i2_l;

			case LWS_CBOR_MAJTYP_FLOAT:
				/*
				 * This can also be a bunch of specials as well
				 * as sizes of float...
				 */
				sm = c & LWS_CBOR_SUBMASK;

				switch (sm) {
				case LWS_CBOR_SWK_FALSE:
					ctx->present = LECPCB_VAL_FALSE;
					goto issue;

				case LWS_CBOR_SWK_TRUE:
					ctx->present = LECPCB_VAL_TRUE;
					goto issue;

				case LWS_CBOR_SWK_NULL:
					ctx->present = LECPCB_VAL_NULL;
					goto issue;

				case LWS_CBOR_SWK_UNDEFINED:
					ctx->present = LECPCB_VAL_UNDEFINED;
					goto issue;

				case LWS_CBOR_M7_SUBTYP_SIMPLE_X8:
					st->s = LECP_SIMPLEX8;
					break;

				case LWS_CBOR_M7_SUBTYP_FLOAT16:
					ctx->present = LECPCB_VAL_FLOAT16;
					ex(ctx, &ctx->item.u.hf, 2);
					break;

				case LWS_CBOR_M7_SUBTYP_FLOAT32:
					ctx->present = LECPCB_VAL_FLOAT32;
					ex(ctx, &ctx->item.u.f, 4);
					break;

				case LWS_CBOR_M7_SUBTYP_FLOAT64:
					ctx->present = LECPCB_VAL_FLOAT64;
					ex(ctx, &ctx->item.u.d, 8);
					break;

				case LWS_CBOR_M7_BREAK:
					if (!ctx->sp ||
					    !ctx->st[ctx->sp - 1].indet)
						goto bad_coding;

					lwcp_completed(ctx, 1);
					break;

				default:
					/* handle as simple */
					ctx->item.u.u64 = (uint64_t)sm;
					if (pst->cb(ctx, LECPCB_VAL_SIMPLE))
						goto reject_callback;
					break;
				}
				break;
			}
			break;

		/*
		 * We're collecting int / float pieces
		 */
		case LECP_COLLECT:
			if (ctx->be) {

				if (ctx->collect_tgt + 1 >= &ctx->item.opcode)
					*ctx->collect_tgt = c;
				else
					*ctx->collect_tgt++ = c;

			} else {

				if (ctx->collect_tgt <= (uint8_t *)&ctx->item.u)
					*ctx->collect_tgt = c;
				else
					*ctx->collect_tgt-- = c;

			}

			if (--st->collect_rem)
				break;

			/*
			 * We collected whatever it was...
			 */

			ctx->npos = 0;
			ctx->buf[0] = '\0';

			switch (st->opcode) {
			case LWS_CBOR_MAJTYP_BSTR:
			case LWS_CBOR_MAJTYP_TSTR:
				st->collect_rem = ctx->item.u.u64;
				if ((!ctx->sp || (ctx->sp &&
				    !ctx->st[ctx->sp - 1].intermediate)) &&
				    pst->cb(ctx, (char)((st->opcode ==
						    LWS_CBOR_MAJTYP_TSTR) ?
							LECPCB_VAL_STR_START :
							LECPCB_VAL_BLOB_START)))
					goto reject_callback;
				st->s = LECP_COLLATE;
				break;

			case LWS_CBOR_MAJTYP_ARRAY:
				st->collect_rem = ctx->item.u.u64;
				lecp_push(ctx, 0, LECPCB_ARRAY_END, LECP_OPC);
				break;

			case LWS_CBOR_MAJTYP_MAP:
				st->collect_rem = ctx->item.u.u64 * 2;
				lecp_push(ctx, 0, LECPCB_OBJECT_END, LECP_OPC);
				break;

			case LWS_CBOR_MAJTYP_TAG:
				st->tag = ctx->item.u.u64;
				goto start_tag_enclosure;

			default:
				/*
				 * ... then issue what we collected as a
				 * literal
				 */

				if (st->opcode == LWS_CBOR_MAJTYP_INT_NEG)
					ctx->item.u.i64 = (-1ll) - ctx->item.u.i64;

				goto issue;
			}
			break;

		case LECP_SIMPLEX8:
			/*
			 * Extended SIMPLE byte for 7|24 opcode, no uses
			 * for it in RFC8949
			 */
			if (c <= LWS_CBOR_INDETERMINITE)
				/*
				 * Duplication of implicit simple values is
				 * denied by RFC8949 3.3
				 */
				goto bad_coding;

			ctx->item.u.u64 = (uint64_t)c;
			if (pst->cb(ctx, LECPCB_VAL_SIMPLE))
				goto reject_callback;

			lwcp_completed(ctx, 0);
			break;

		case LECP_COLLATE:
			/*
			 * let's grab b/t string content into the context
			 * buffer, and issue chunks from there
			 */

			ctx->buf[ctx->npos++] = (char)c;
			if (st->collect_rem)
				st->collect_rem--;

			/* spill at chunk boundaries, or if we filled the buf */
			if (ctx->npos != sizeof(ctx->buf) - 1 &&
			    st->collect_rem)
				break;

			/* spill */
			ctx->buf[ctx->npos] = '\0';

			/* if it's a map name, deal with the path */
			if (ctx->sp && lecp_parse_map_is_key(ctx)) {
				if (lwcp_st_parent(ctx)->ordinal)
					pst->ppos = st->p;
				st->p = pst->ppos;
				if (pst->ppos + ctx->npos >= sizeof(ctx->path))
					goto reject_overflow;
				memcpy(&ctx->path[pst->ppos], ctx->buf,
				       (size_t)(ctx->npos + 1));
				pst->ppos = (uint8_t)(pst->ppos + ctx->npos);
				lecp_check_path_match(ctx);
			}

			to = 0;
			if (ctx->item.opcode == LWS_CBOR_MAJTYP_BSTR)
				to = LECPCB_VAL_BLOB_END - LECPCB_VAL_STR_END;

			o = (uint8_t)(LECPCB_VAL_STR_END + to);
			c = (st->collect_rem /* more to come at this layer */ ||
			    /* we or direct parent is indeterminite */
			    lwcp_is_indet_string(ctx));

			if (ctx->sp)
				ctx->st[ctx->sp - 1].intermediate = !!c;
			if (c)
				o--;

			if (pst->cb(ctx, (char)o))
				goto reject_callback;
			ctx->npos = 0;
			ctx->buf[0] = '\0';

			if (ctx->sp && lwcp_st_parent(ctx)->indet)
				st->s = LECP_OPC;
			if (o == LECPCB_VAL_STR_END + to)
				lwcp_completed(ctx, 0);

			break;

		case LECP_ONLY_SAME:
			/*
			 * deterministic sized chunks same MAJTYP as parent
			 * level only (BSTR and TSTR frags inside interderminite
			 * BSTR or TSTR)
			 *
			 * Clean end when we see M7|31
			 */
			if (!ctx->sp) {
				/*
				 * We should only come here by pushing on stack
				 */
				assert(0);
				return LECP_STACK_OVERFLOW;
			}

			if (c == (LWS_CBOR_MAJTYP_FLOAT | LWS_CBOR_M7_BREAK)) {
				/* if's the end of an interdetminite list */
				if (!ctx->sp || !ctx->st[ctx->sp - 1].indet)
					/*
					 * Can't have a break without an
					 * indeterminite parent
					 */
					goto bad_coding;

				if (lwcp_completed(ctx, 1))
					goto reject_callback;
				break;
			}

			if (st->opcode != lwcp_st_parent(ctx)->opcode)
				/*
				 * Fragments have to be of the same type as the
				 * outer opcode
				 */
				goto bad_coding;

			sm = c & LWS_CBOR_SUBMASK;

			if (sm == LWS_CBOR_INDETERMINITE)
				/* indeterminite length frags not allowed */
				goto bad_coding;

			if (sm < LWS_CBOR_1) {
				st->indet = 0;
				st->collect_rem = (uint64_t)sm;
				st->s = LECP_COLLATE;
				break;
			}

			if (sm >= LWS_CBOR_RESERVED)
				goto bad_coding;

			goto i2_l;

		default:
			assert(0);
			return -1;
		}

		continue;

start_tag_enclosure:
		st->p = pst->ppos;
		ret = lecp_push(ctx, LECPCB_TAG_START, LECPCB_TAG_END, LECP_OPC);
		if (ret)
			return ret;

		continue;

issue:
		if (ctx->item.opcode == LWS_CBOR_MAJTYP_TAG) {
			st->tag = ctx->item.u.u64;
			goto start_tag_enclosure;
		}

		/* we are just a number */

		if (pst->cb(ctx, ctx->present))
			goto reject_callback;

		lwcp_completed(ctx, 0);

	}

	ctx->used_in = olen - len;

	if (!ctx->sp && ctx->st[0].s == LECP_OPC)
		return 0;

	return LECP_CONTINUE;

reject_overflow:
	ret = LECP_STACK_OVERFLOW;
	goto reject;

bad_coding:
	ret = LECP_REJECT_BAD_CODING;
	goto reject;

reject_callback:
	ret = LECP_REJECT_CALLBACK;

reject:
	ctx->pst[ctx->pst_sp].cb(ctx, LECPCB_FAILED);

	return ret;
}



void
lws_lec_init(lws_lec_pctx_t *ctx, uint8_t *buf, size_t len)
{
	memset(ctx, 0, sizeof(*ctx));
	ctx->start = ctx->buf = buf;
	ctx->end = ctx->start + len;
	ctx->fmt_pos = 0;
}

void
lws_lec_setbuf(lws_lec_pctx_t *ctx, uint8_t *buf, size_t len)
{
	ctx->start = ctx->buf = buf;
	ctx->end = ctx->start + len;
	ctx->used = 0;
	ctx->vaa_pos = 0;
}

enum lws_lec_pctx_ret
lws_lec_printf(lws_lec_pctx_t *ctx, const char *format, ...)
{
	enum lws_lec_pctx_ret r;
	va_list ap;

	va_start(ap, format);
	r = lws_lec_vsprintf(ctx, format, ap);
	va_end(ap);

	return r;
}

/*
 * Report how many next-level elements inbetween fmt[0] and the matching
 * closure, eg, [] returns 0,  [123] would return 1, [123,456] returns 2, and
 * [123,{'a':[123,456]}] returns 2.  Counts for { } maps are in pairs, ie,
 * {'a':1, 'b': 2} returns 2
 *
 * If there is no closure in the string it returns -1
 *
 * We use this to figure out if we should use indeterminite lengths or specific
 * lengths for items in the format string
 */

#define bump(_r) count[sp]++
//; lwsl_notice("%s: count[%d] -> %d\n", _r, sp, count[sp])

static int
format_scan(const char *fmt)
{
	char stack[12], literal = 0, numeric = 0;
	int count[12], sp = 0, pc = 0, swallow = 0;

	literal = *fmt == '\'';
	stack[sp] = *fmt++;
	count[sp] = 0;

//	lwsl_notice("%s: start %s\n", __func__, fmt - 1);

	while (*fmt) {

//		lwsl_notice("%s: %c %d %d\n", __func__, *fmt, sp, literal);

		if (swallow) {
			swallow--;
			fmt++;
			continue;
		}

		if (numeric) {
			while (*fmt >= '0' && *fmt <= '9')
				fmt++;
			numeric = 0;
			if (*fmt != '(')
				bump("a");
		}

		if (literal) {
			if (*fmt == '\\' && fmt[1]) {
				fmt += 2;
				continue;
			}
			if (*fmt == '\'') {
				literal = 0;
				if (!sp && stack[sp] == '\'')
					return count[sp];

				if (sp)
					sp--;
				fmt++;
				continue;
			}

			bump("b");
			fmt++;
			continue;
		}

		if (*fmt == '\'') {
			bump("c");
			sp++;
			literal = 1;
			fmt++;
			continue;
		}

		switch (pc) {
		case 1:
			if (*fmt == '.') {
				pc++;
				fmt++;
				continue;
			}
			if (*fmt == 'l') {
				pc++;
				fmt++;
				continue;
			}
			/* fallthru */
		case 2:
			if (*fmt == '*') {
				pc++;
				fmt++;
				continue;
			}
			if (*fmt == 'l') {
				pc++;
				fmt++;
				continue;
			}
			/* fallthru */
		case 3:
			bump("pc");
			pc = 0;
			fmt++;
			continue;
		}

		switch (*fmt) {

		case '<':
			swallow = 1;
			/* fallthru */
		case '[':
		case '(':
		case '{':
			if (sp == sizeof(stack))
				return -2;

			bump("d");
			sp++;
			stack[sp] = *fmt;
			count[sp] = 0;
			break;
		case ' ':
			break;
		case ',':
			//count[sp]++;
			break;
		case ':':
			if (stack[sp] != '{')
				goto mismatch;
			//count[sp]++;
			break;
		case '%':
			pc = 1;
			break;
		case ']':
			if (stack[sp] != '[')
				goto mismatch;
			goto pop_l;
		case ')':
			if (stack[sp] != '(')
				goto mismatch;
			goto pop_l;
		case '}':
			if (stack[sp] != '{')
				goto mismatch;
			goto pop_l;
		case '>':
			if (stack[sp] != '<')
				goto mismatch;
pop_l:
			if (sp) {
				sp--;
				break;
			}

			if (stack[0] == '{') {
				/* args have to come in pairs */
				if (count[0] & 1) {
					lwsl_err("%s: odd map args %d %s\n",
							__func__, count[0], fmt);
					return -2;
				}
				// lwsl_notice("%s: return %d pairs\n", __func__, count[0] >> 1);
				/* report how many pairs */
				return count[0] >> 1;
			}

			// lwsl_notice("%s: return %d items\n", __func__, count[0]);

			return count[0];

		case '-':
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			numeric = 1;

			break;

		default:
			bump("e");
			break;
		}
		fmt++;
	}

	return -1;

mismatch:
	lwsl_err("%s: format mismatch %c %c\n", __func__, stack[sp], *fmt);

	return -2;
}

void
lws_lec_signed(lws_lec_pctx_t *ctx, int64_t num)
{
	if (num < 0)
		lws_lec_int(ctx, LWS_CBOR_MAJTYP_INT_NEG, 0,
					(uint64_t)(-1ll - num));
	else
		lws_lec_int(ctx, LWS_CBOR_MAJTYP_UINT, 0, (uint64_t)num);
}

void
lws_lec_int(lws_lec_pctx_t *ctx, uint8_t opcode, uint8_t indet, uint64_t num)
{
	uint8_t hint = 0;
	unsigned int n;

	if (indet) {
		ctx->scratch[ctx->scratch_len++] = (uint8_t)(opcode |
							LWS_CBOR_INDETERMINITE);
		return;
	}

	if ((opcode & LWS_CBOR_MAJTYP_MASK) == LWS_CBOR_MAJTYP_FLOAT) {
		hint = opcode & LWS_CBOR_SUBMASK;
		switch (hint) {
		case LWS_CBOR_M7_SUBTYP_FLOAT16:
			num <<= 48;
			break;
		case LWS_CBOR_M7_SUBTYP_FLOAT32:
			num <<= 32;
			break;
		}
	} else {

		if (num < LWS_CBOR_1) {
			ctx->scratch[ctx->scratch_len++] = (uint8_t)(opcode | num);
			return;
		}

		if (!(num & (uint64_t)(~0xffull))) {
			hint = LWS_CBOR_1;
			num <<= 56;
		} else
			if (!(num & (uint64_t)(~0xffffull))) {
				hint = LWS_CBOR_2;
				num <<= 48;
			} else
				if (!(num & (uint64_t)(~0xffffffffull))) {
					hint = LWS_CBOR_4;
					num <<= 32;
				}
				else
					hint = LWS_CBOR_8;
	}

	ctx->scratch[ctx->scratch_len++] = (uint8_t)(opcode | hint);
	n = 1u << (hint - LWS_CBOR_1);
	while (n) {
		n--; /* cov */
		ctx->scratch[ctx->scratch_len++] = (uint8_t)(num >> 56);
		num <<= 8;
	}
}

enum {
	NATTYPE_INT,
	NATTYPE_LONG,
	NATTYPE_LONG_LONG,
	NATTYPE_PTR,
	NATTYPE_DOUBLE,
};

int
lws_lec_scratch(lws_lec_pctx_t *ctx)
{
	size_t s;

	if (!ctx->scratch_len)
		return 0;

	s = lws_ptr_diff_size_t(ctx->end, ctx->buf);
	if (s > (size_t)ctx->scratch_len)
		s = (size_t)ctx->scratch_len;

	memcpy(ctx->buf, ctx->scratch, s);
	ctx->buf += s;
	ctx->scratch_len = (uint8_t)(ctx->scratch_len - (uint8_t)s);

	return ctx->buf == ctx->end;
}

enum lws_lec_pctx_ret
lws_lec_vsprintf(lws_lec_pctx_t *ctx, const char *fmt, va_list args)
{
	size_t fl = strlen(fmt);
	uint64_t u64 = 0;
	int64_t i64;
#if defined(LWS_WITH_CBOR_FLOAT)
	double dbl;
#endif
	size_t s;
	char c;
	int n;

	/*
	 * We might be being called after the first time, since we had to emit
	 * output buffer(s) before we could move on in the format string.  For
	 * this case, reposition ourselves at the vaarg we got to from the last
	 * call.
	 */

	for (n = 0; n < ctx->vaa_pos; n++) {

		switch (ctx->vaa[n]) {
		case NATTYPE_INT:
			(void)va_arg(args, int);
			break;
		case NATTYPE_LONG:
			(void)va_arg(args, long);
			break;
		case NATTYPE_LONG_LONG:
			(void)va_arg(args, long long);
			break;
		case NATTYPE_PTR:
			(void)va_arg(args, const char *);
			break;
		case NATTYPE_DOUBLE:
			(void)va_arg(args, double);
			break;
		}
		if (ctx->state == CBPS_STRING_BODY)
			/*
			 * when copying out text or binary strings, we reload
			 * the %s or %.*s pointer on subsequent calls, in case
			 * it was on the stack.  The length and contents should
			 * not change between calls, but it's OK if the source
			 * address does.
			 */
			ctx->ongoing_src = va_arg(args, uint8_t *);
	}

	while (ctx->buf != ctx->end) {

		/*
		 * We write small things into the context scratch array, then
		 * copy that into the output buffer fragmenting as needed.  Next
		 * time we will finish emptying the scratch into the output
		 * buffer preferentially.
		 *
		 * Then we don't otherwise have to handle fragmentations in
		 * order to exactly fill the output buffer, simplifying
		 * everything else.
		 */

		if (lws_lec_scratch(ctx))
			break;

		if (ctx->fmt_pos >= fl) {
			if (ctx->state == CBPS_IDLE)
				break;
			c = 0;
		} else
			c = fmt[ctx->fmt_pos];

		// lwsl_notice("%s: %d %d %c\n", __func__, ctx->state, ctx->sp, c);

		switch (ctx->state) {
		case CBPS_IDLE:
			ctx->scratch_len = 0;
			switch (c) {
			case '[':
				n = format_scan(&fmt[ctx->fmt_pos]);
				if (n == -2)
					return LWS_LECPCTX_RET_FAIL;
				lws_lec_int(ctx, LWS_CBOR_MAJTYP_ARRAY, n == -1,
							(uint64_t)n);
				goto stack_push_l;
			case '{':
				n = format_scan(&fmt[ctx->fmt_pos]);
				if (n == -2)
					return LWS_LECPCTX_RET_FAIL;
				lws_lec_int(ctx, LWS_CBOR_MAJTYP_MAP, n == -1,
							(uint64_t)n);
				goto stack_push_l;
			case '(':
				/* must be preceded by a number */
				goto fail;

			case '<': /* <t or <b */
				ctx->state = CBPS_CONTYPE;
				break;

			case ']':
				if (!ctx->sp || ctx->stack[ctx->sp - 1] != '[')
					return LWS_LECPCTX_RET_FAIL;
				ctx->sp--;
				break;
			case '}':
				if (!ctx->sp || ctx->stack[ctx->sp - 1] != '{')
					return LWS_LECPCTX_RET_FAIL;
				ctx->sp--;
				break;
			case ')':
				if (!ctx->sp || ctx->stack[ctx->sp - 1] != '(') {
					lwsl_notice("bad tag end %d %c\n",
						ctx->sp, ctx->stack[ctx->sp - 1]);
					goto fail;
				}
				ctx->sp--;
				break;
			case '>':
				if (!ctx->sp || ctx->stack[ctx->sp - 1] != '<')
					return LWS_LECPCTX_RET_FAIL;
				ctx->scratch[ctx->scratch_len++] =
						(uint8_t)(LWS_CBOR_MAJTYP_FLOAT |
							LWS_CBOR_M7_BREAK);
				ctx->sp--;
				break;
			case '\'':
				n = format_scan(&fmt[ctx->fmt_pos]);
				// lwsl_notice("%s: quote fs %d\n", __func__, n);
				if (n < 0)
					return LWS_LECPCTX_RET_FAIL;
				lws_lec_int(ctx, LWS_CBOR_MAJTYP_TSTR, 0,
								(uint64_t)n);
				ctx->state = CBPS_STRING_LIT;
				break;
			case '%':
				if (ctx->vaa_pos >= sizeof(ctx->vaa) - 1) {
					lwsl_err("%s: too many %%\n", __func__);
					goto fail;
				}
				ctx->_long = 0;
				ctx->dotstar = 0;
				ctx->state = CBPS_PC1;
				break;
			case ':':
				break;
			case ',':
				break;
			case '-':
				ctx->item.opcode = LWS_CBOR_MAJTYP_INT_NEG;
				ctx->item.u.i64 = 0;
				ctx->state = CBPS_NUM_LIT;
				break;
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				ctx->item.opcode = LWS_CBOR_MAJTYP_UINT;
				ctx->item.u.u64 = (uint64_t)(c - '0');
				ctx->state = CBPS_NUM_LIT;
				break;
			}
			break;
		case CBPS_PC1:
			if (c == 'l') {
				ctx->_long++;
				ctx->state = CBPS_PC2;
				break;
			}
			if (c == '.') {
				ctx->dotstar++;
				ctx->state = CBPS_PC2;
				break;
			}
			/* fallthru */

		case CBPS_PC2:
			if (c == 'l') {
				ctx->_long++;
				ctx->state = CBPS_PC3;
				break;
			}
			if (c == '*') {
				ctx->dotstar++;
				ctx->state = CBPS_PC3;
				break;
			}
			/* fallthru */

		case CBPS_PC3:
			switch (c) {
			case 'd':
				switch (ctx->_long) {
				case 0:
					i64 = (int64_t)va_arg(args, int);
					ctx->vaa[ctx->vaa_pos++] = NATTYPE_INT;
					break;
				case 1:
					i64 = (int64_t)va_arg(args, long);
					ctx->vaa[ctx->vaa_pos++] = NATTYPE_LONG;
					break;
				case 2:
					i64 = (int64_t)va_arg(args, long long);
					ctx->vaa[ctx->vaa_pos++] = NATTYPE_LONG_LONG;
					break;
				default:
					i64 = 0;
					break;
				}
				if (i64 < 0)
					lws_lec_int(ctx,
						    LWS_CBOR_MAJTYP_INT_NEG, 0,
						    (uint64_t)(-1ll - i64));
				else
					lws_lec_int(ctx,
						    LWS_CBOR_MAJTYP_UINT, 0,
						    (uint64_t)i64);
				break;
			case 'u':
				switch (ctx->_long) {
				case 0:
					u64 = (uint64_t)va_arg(args, unsigned int);
					ctx->vaa[ctx->vaa_pos++] = NATTYPE_INT;
					break;
				case 1:
					u64 = (uint64_t)va_arg(args, unsigned long);
					ctx->vaa[ctx->vaa_pos++] = NATTYPE_LONG;
					break;
				case 2:
					u64 = (uint64_t)va_arg(args, unsigned long long);
					ctx->vaa[ctx->vaa_pos++] = NATTYPE_LONG_LONG;
					break;
				default:
					i64 = 0;
					break;
				}
				lws_lec_int(ctx, LWS_CBOR_MAJTYP_UINT, 0, u64);
				break;
			case 's': /* text string */
				ctx->ongoing_done = 0;
				if (ctx->dotstar == 2) {
					ctx->ongoing_len = (uint64_t)va_arg(args, int);
					ctx->vaa[ctx->vaa_pos++] = NATTYPE_INT;
				}
				/* vaa for ptr done at end of body copy */
				ctx->ongoing_src = va_arg(args, uint8_t *);
				if (ctx->dotstar != 2)
					ctx->ongoing_len = (uint64_t)strlen(
						(const char *)ctx->ongoing_src);
				lws_lec_int(ctx, LWS_CBOR_MAJTYP_TSTR, 0, ctx->ongoing_len);
				ctx->state = CBPS_STRING_BODY;
				ctx->fmt_pos++;
				continue;
			case 'b': /* binary string (%.*b only) */
				if (ctx->dotstar != 2)
					goto fail;
				ctx->vaa[ctx->vaa_pos++] = NATTYPE_INT;
				ctx->ongoing_done = 0;
				ctx->ongoing_len = (uint64_t)va_arg(args, int);
				/* vaa for ptr done at end of body copy */
				ctx->ongoing_src = va_arg(args, uint8_t *);
				lws_lec_int(ctx, LWS_CBOR_MAJTYP_BSTR, 0, ctx->ongoing_len);
				ctx->state = CBPS_STRING_BODY;
				ctx->fmt_pos++;
				continue;
			case 't': /* dynamic tag */
				switch (ctx->_long) {
				case 0:
					ctx->item.u.u64 = (uint64_t)va_arg(args, int);
					ctx->vaa[ctx->vaa_pos++] = NATTYPE_INT;
					break;
				case 1:
					ctx->item.u.u64 = (uint64_t)va_arg(args, long);
					ctx->vaa[ctx->vaa_pos++] = NATTYPE_LONG;
					break;
				case 2:
					ctx->item.u.u64 = (uint64_t)va_arg(args, long long);
					ctx->vaa[ctx->vaa_pos++] = NATTYPE_LONG_LONG;
					break;
				default:
					i64 = 0;
					break;
				}
				ctx->item.opcode = LWS_CBOR_MAJTYP_UINT;
				ctx->fmt_pos++;
				if (ctx->fmt_pos >= fl)
					continue;
				c = fmt[ctx->fmt_pos];
				if (c != '(')
					goto fail;
				goto tag_body_l;
#if defined(LWS_WITH_CBOR_FLOAT)
			case 'f': /* floating point double */
				dbl = va_arg(args, double);

				if (dbl == (float)dbl) {
					uint16_t hf;
					union {
						uint32_t ui;
						float f;
					} u1, u2;

					u1.f = (float)dbl;
					lws_singles2halfp(&hf, u1.ui);
					lws_halfp2singles(&u2.ui, hf);

					if ((isinf(u1.f) && isinf(u2.f)) ||
					    (isnan(u1.f) && isnan(u2.f)) ||
					    u1.f == u2.f) {
						lws_lec_int(ctx,
							    LWS_CBOR_MAJTYP_FLOAT |
							    LWS_CBOR_M7_SUBTYP_FLOAT16,
							    0, hf);
						break;
					}
					/* do it as 32-bit float */
					lws_lec_int(ctx,
						    LWS_CBOR_MAJTYP_FLOAT |
						    LWS_CBOR_M7_SUBTYP_FLOAT32,
						    0, u1.ui);
					break;
				}

				/* do it as 64-bit double */

				{
					union {
						uint64_t ui;
						double f;
					} u3;

					u3.f = dbl;
					lws_lec_int(ctx,
						    LWS_CBOR_MAJTYP_FLOAT |
						    LWS_CBOR_M7_SUBTYP_FLOAT64,
						    0, u3.ui);
				}
				break;
#else
			case 'f':
				lwsl_err("%s: no FP support\n", __func__);
				goto fail;
#endif
			}
			ctx->state = CBPS_IDLE;
			break;

		case CBPS_STRING_BODY:
			s = lws_ptr_diff_size_t(ctx->end, ctx->buf);
			if (s > (size_t)(ctx->ongoing_len - ctx->ongoing_done))
				s = (size_t)(ctx->ongoing_len - ctx->ongoing_done);
			memcpy(ctx->buf, ctx->ongoing_src + ctx->ongoing_done, s);
			ctx->buf += s;
			ctx->ongoing_done += s;
			if (ctx->ongoing_len == ctx->ongoing_done) {
				/* vaa for ptr */
				ctx->vaa[ctx->vaa_pos++] = NATTYPE_PTR;
				ctx->state = CBPS_IDLE;
			}
			continue;

		case CBPS_NUM_LIT:
			if (c >= '0' && c <= '9') {
				ctx->item.u.u64 = (ctx->item.u.u64 * 10) +
								(uint64_t)(c - '0');
				break;
			}

			if (ctx->item.opcode == LWS_CBOR_MAJTYP_INT_NEG)
				ctx->item.u.i64--;

			if (c == '(') { /* tag qualifier */
tag_body_l:
				n = format_scan(&fmt[ctx->fmt_pos]);
				if (n == -2)
					goto fail;
				/*
				 * inteterminite length not possible for tag,
				 * take it to mean that the closure is in a
				 * later format string
				 */

				lws_lec_int(ctx, LWS_CBOR_MAJTYP_TAG, 0,
							ctx->item.u.u64);

stack_push_l:
				if (ctx->sp >= sizeof(ctx->stack))
					return LWS_LECPCTX_RET_FAIL;
				ctx->stack[ctx->sp] = (uint8_t)c;
				ctx->indet[ctx->sp++] = (uint8_t)(n == -1);
				// lwsl_notice("%s: pushed %c\n", __func__, c);
				ctx->state = CBPS_IDLE;
				break;
			}

			lws_lec_int(ctx, ctx->item.opcode, 0, ctx->item.u.u64);

			ctx->state = CBPS_IDLE;
			/* deal with the terminating char fresh */
			continue;

		case CBPS_STRING_LIT:
			if (!ctx->escflag && c == '\\') {
				ctx->escflag = 1;
				break;
			}
			if (!ctx->escflag && c == '\'') {
				ctx->state = CBPS_IDLE;
				break;
			}

			*ctx->buf++ = (uint8_t)c;
			ctx->escflag = 0;

			break;

		case CBPS_CONTYPE:
			if (c != 't' && c != 'b')
				return LWS_LECPCTX_RET_FAIL;

			lws_lec_int(ctx, c == 't' ? LWS_CBOR_MAJTYP_TSTR :
						    LWS_CBOR_MAJTYP_BSTR, 1, 0);
			c = '<';
			n = 0;
			goto stack_push_l;
		}

		ctx->fmt_pos++;
	}

	ctx->used = lws_ptr_diff_size_t(ctx->buf, ctx->start);
	// lwsl_notice("%s: ctx->used %d\n", __func__, (int)ctx->used);

	if (ctx->buf == ctx->end || ctx->scratch_len)
		return LWS_LECPCTX_RET_AGAIN;

	ctx->fmt_pos = 0;
	ctx->vaa_pos = 0;

	return LWS_LECPCTX_RET_FINISHED;

fail:
	lwsl_notice("%s: failed\n", __func__);

	ctx->fmt_pos = 0;

	return LWS_LECPCTX_RET_FAIL;
}
