1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-23 00:00:06 +01:00
libwebsockets/lib/misc/lecp.c
Andy Green dcaa0013b4 lecp: add CBOR stream parser LECP like JSON LEJP
This provides very memory-efficient CBOR stream parsing
and writing.

The parser  converts pieces of CBOR into callbacks that define
the structure and collate string and blobs into buffer chunks
for extensible and easy access.

It is fragementation-safe and does not need all the CBOR in
the same place at one time, chunks of CBOR are parsed and
discarded as provided.

It does not allocate and just needs a few hundred bytes of
stack for even huge CBOR objects.  Huge strings and blobs
are handled without needing memory to hold them atomically.

Includes ./minimal-examples/api-tests/api-test-lecp that
unit tests it against 82 official example CBORs and
26 additional test vectors from COSE (just checking the CBOR
parsing).

The writing apis allow printf style semantics with a variety
of CBOR-aware %-formats.  The apis write into a context that
manages output buffer usage, if the output buffer fills,
then the apis return with an AGAIN code that lets you issue
and reset the output buffer and repeat the api all to issue
more output.  The subsequent calls can occur much later or
from a different function context, so this is perfect for
WRITEABLE-mediated output from the network parts of lws.

See ./READMEs/README.cbor-lecp.md
2021-08-21 17:44:40 +01:00

1686 lines
35 KiB
C

/*
* 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;
if (pst->cb(ctx, LECPCB_LITERAL_CBOR))
return 1;
ctx->cbor_pos = 0;
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) {
ctx->cbor[ctx->cbor_pos++] = c;
if (ctx->cbor_pos == sizeof(ctx->cbor) &&
report_raw_cbor(ctx))
goto reject_callback;
}
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;
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:
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;
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;
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';
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;
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;
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;
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)
*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;
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) {
if (*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;
case ')':
if (stack[sp] != '(')
goto mismatch;
goto pop;
case '}':
if (stack[sp] != '{')
goto mismatch;
goto pop;
case '>':
if (stack[sp] != '<')
goto mismatch;
pop:
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 '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--) {
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;
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;
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;
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;
}
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;
}
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;
}
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;
#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:
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:
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;
}
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;
}