mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-11-24 01:39:33 +00:00
1708 lines
35 KiB
C
1708 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) {
|
|
|
|
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;
|
|
|
|
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;
|
|
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 '-':
|
|
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 = 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;
|
|
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;
|
|
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;
|
|
#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;
|
|
}
|