/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2010 - 2021 Andy Green * * 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 #include #if defined(LWS_WITH_CBOR_FLOAT) #include #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 '<': /* 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; }