From 8d5fcfc6e82fa87e14f0e80fd4e2da5bf9fb517a Mon Sep 17 00:00:00 2001 From: Maria Matejka Date: Thu, 3 Oct 2024 23:02:34 +0200 Subject: [PATCH] Lib: added a generic CBOR parser framework --- lib/Makefile | 2 +- lib/cbor-parser.c | 180 ++++++++++++++++++++++++++++++++++++++++++++++ lib/cbor.c | 18 +++++ lib/cbor.h | 76 ++++++++++++++++++++ 4 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 lib/cbor-parser.c diff --git a/lib/Makefile b/lib/Makefile index 24bb0af1..963b9061 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -1,4 +1,4 @@ -src := bitmap.c bitops.c blake2s.c blake2b.c cbor.c cbor_parse_tools.c cbor_shortcuts.c checksum.c defer.c event.c flowspec.c idm.c ip.c lists.c lockfree.c mac.c md5.c mempool.c net.c netindex.c patmatch.c printf.c rcu.c resource.c runtime.c sha1.c sha256.c sha512.c slab.c slists.c strtoul.c tbf.c timer.c xmalloc.c +src := bitmap.c bitops.c blake2s.c blake2b.c cbor.c cbor-parser.c cbor_parse_tools.c cbor_shortcuts.c checksum.c defer.c event.c flowspec.c idm.c ip.c lists.c lockfree.c mac.c md5.c mempool.c net.c netindex.c patmatch.c printf.c rcu.c resource.c runtime.c sha1.c sha256.c sha512.c slab.c slists.c strtoul.c tbf.c timer.c xmalloc.c obj := $(src-o-files) $(all-lib) diff --git a/lib/cbor-parser.c b/lib/cbor-parser.c new file mode 100644 index 00000000..caa3c728 --- /dev/null +++ b/lib/cbor-parser.c @@ -0,0 +1,180 @@ +/* + * BIRD CBOR parser + * + * (c) 2024 Maria Matejka + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "lib/birdlib.h" +#include "lib/cbor.h" + +struct cbor_parser_context * +cbor_parser_new(pool *p, uint stack_max_depth) +{ + linpool *lp = lp_new(p); + struct cbor_parser_context *ctx = lp_allocz( + lp, sizeof *ctx + (stack_max_depth + 1) * sizeof ctx->stack_countdown[0]); + + ctx->lp = lp; + ctx->flush = lp_save(lp); + + ctx->type = 0xff; + ctx->stack_countdown[0] = 1; + ctx->stack_max = stack_max_depth; + + return ctx; +} + +void cbor_parser_reset(struct cbor_parser_context *ctx) +{ + lp_restore(ctx->lp, ctx->flush); + ctx->flush = lp_save(ctx->lp); + + ctx->type = 0xff; + ctx->target_buf = NULL; + ctx->target_len = 0; + ctx->error = NULL; + ctx->partial_state = CPE_TYPE; + ctx->partial_countdown = 0; + ctx->stack_pos = 0; + ctx->stack_countdown[0] = 1; +} + +#define CBOR_PARSER_ERROR(...) do { \ + ctx->error = lp_sprintf(ctx->lp, __VA_ARGS__);\ + return CPR_ERROR; \ +} while (0) + +enum cbor_parse_result +cbor_parse_byte(struct cbor_parser_context *ctx, const byte bp) +{ + ctx->tflags = 0; + + switch (ctx->partial_state) + { + case CPE_EXIT: + CBOR_PARSER_ERROR("Trailing byte %02x", bp); + + case CPE_ITEM_DONE: + bug("You have to check cbor_parse_block_end() before running cbor_parse_byte()"); + + case CPE_TYPE: + /* Split the byte to type and value */ + ctx->type = bp >> 5; + ctx->value = bp & 0x1f; + + if (ctx->type == 7) + { + if (ctx->value < 20) + CBOR_PARSER_ERROR("Unknown simple value %u", ctx->value); + else if (ctx->value < 24) + ; /* false, true, null, undefined */ + else if (ctx->value < 28) + { + /* Need more data */ + ctx->partial_state = CPE_READ_INT; + ctx->partial_countdown = (1 << (ctx->value - 24)); + ctx->value = 0; + break; + } + else if (ctx->value == 31) + ; /* break-stop */ + else + CBOR_PARSER_ERROR("Unknown simple value %u", ctx->value); + } + else + { + if (ctx->value < 24) + ; /* Immediate value, fall through */ + else if (ctx->value < 28) + { + /* Need more data */ + ctx->partial_state = CPE_READ_INT; + ctx->partial_countdown = (1 << (ctx->value - 24)); + ctx->value = 0; + break; + } + else if ((ctx->value == 31) && (ctx->type >= 2) && (ctx->type <= 5)) + /* Indefinite length, fall through */ + ctx->tflags |= CPT_VARLEN; + else + CBOR_PARSER_ERROR("Garbled additional value %u for type %u", ctx->value, ctx->type); + } + /* fall through */ + + case CPE_READ_INT: + if (ctx->partial_state == CPE_READ_INT) + { + /* Reading a network order integer */ + ctx->value <<= 8; + ctx->value |= bp; + if (--ctx->partial_countdown) + break; + } + /* fall through */ + + case CPE_COMPLETE_INT: + /* Some types are completely parsed, some not yet */ + switch (ctx->type) + { + case 0: + case 1: + case 7: + ctx->partial_state = CPE_ITEM_DONE; + break; + + case 2: + case 3: + ctx->partial_state = CPE_READ_BYTE; + ctx->partial_countdown = ctx->value; + break; + + case 4: + case 5: + if (++ctx->stack_pos >= ctx->stack_max) + CBOR_PARSER_ERROR("Stack too deep"); + + /* set array/map size; + * once for arrays, twice for maps; + * ~0 for indefinite, plus one for the array/map head itself */ + ctx->stack_countdown[ctx->stack_pos] = (ctx->tflags & CPT_VARLEN) ? ~0ULL : + (ctx->value * (ctx->type - 3)) ; + ctx->partial_state = CPE_TYPE; + break; + } + + /* Process the value */ + return CPR_MAJOR; + + case CPE_READ_BYTE: + *ctx->target_buf = bp; + ctx->target_buf++; + if (--ctx->target_len) + break; + + ctx->target_buf = NULL; + ctx->partial_state = CPE_ITEM_DONE; + return CPR_STR_END; + } + + return CPR_MORE; +} + +bool +cbor_parse_block_end(struct cbor_parser_context *ctx) +{ + if (ctx->partial_state != CPE_ITEM_DONE) + return false; + + if (--ctx->stack_countdown[ctx->stack_pos]) + { + ctx->partial_state = CPE_TYPE; + return false; + } + + if (!ctx->stack_pos--) + ctx->partial_state = CPE_EXIT; + + return true; +} diff --git a/lib/cbor.c b/lib/cbor.c index a50319ec..cdc1be2a 100644 --- a/lib/cbor.c +++ b/lib/cbor.c @@ -3,6 +3,24 @@ #include "lib/cbor.h" +static const char *cbor_type_str_a[] = { + "POSINT", + "NEGINT", + "BYTES", + "TEXT", + "ARRAY", + "MAP", + "TAG", + "SPECIAL", +}; + +const char * +cbor_type_str(enum cbor_basic_type t) +{ + return (t < ARRAY_SIZE(cbor_type_str_a)) ? + cbor_type_str_a[t] : + tmp_sprintf("(unknown: %u)", t); +} void write_item(struct cbor_writer *writer, uint8_t major, uint64_t num); void check_memory(struct cbor_writer *writer, int add_size); diff --git a/lib/cbor.h b/lib/cbor.h index 36c16f68..f71ca9fb 100644 --- a/lib/cbor.h +++ b/lib/cbor.h @@ -3,6 +3,18 @@ #include "nest/bird.h" +enum cbor_basic_type { + CBOR_POSINT = 0, + CBOR_NEGINT = 1, + CBOR_BYTES = 2, + CBOR_TEXT = 3, + CBOR_ARRAY = 4, + CBOR_MAP = 5, + CBOR_TAG = 6, + CBOR_SPECIAL = 7, +}; + +const char *cbor_type_str(enum cbor_basic_type); struct cbor_writer { int pt; // where will next byte go @@ -55,4 +67,68 @@ void cbor_write_item_with_constant_val_length_4(struct cbor_writer *writer, uint void rewrite_4bytes_int(struct cbor_writer *writer, int pt, int num); +/* + * Parser bits + */ + +struct cbor_parser_context { + /* Public part */ + linpool *lp; /* Linpool for in-parser allocations */ + + byte type; /* Last parsed type */ + enum { + CPT_VARLEN = 1, + } tflags; /* Additional flags for the type / value pair */ + u64 value; /* Last parsed (integer) value */ + + byte *target_buf; /* Target buf for CBOR_BYTES or CBOR_TEXT */ + uint target_len; /* Set how many bytes to store */ + + const char *error; /* Error message */ + + /* Private part */ + lp_state *flush; /* Linpool reset pointer */ + + enum { /* Multi-byte reader */ + CPE_TYPE = 0, + CPE_READ_INT, + CPE_COMPLETE_INT, + CPE_READ_BYTE, + CPE_ITEM_DONE, + CPE_EXIT, + } partial_state; + + u64 partial_countdown; /* How many items remaining in CBOR_ARRAY / CBOR_MAP */ + + uint stack_pos, stack_max; /* Nesting of CBOR_ARRAY / CBOR_MAP */ + u64 stack_countdown[0]; +}; + +struct cbor_parser_context *cbor_parser_new(pool *, uint stack_max_depth); +static inline void cbor_parser_free(struct cbor_parser_context *ctx) +{ rfree(ctx->lp); } +void cbor_parser_reset(struct cbor_parser_context *ctx); + +enum cbor_parse_result { + CPR_ERROR = 0, + CPR_MORE = 1, + CPR_MAJOR = 2, + CPR_STR_END = 3, +} cbor_parse_byte(struct cbor_parser_context *, const byte); +bool cbor_parse_block_end(struct cbor_parser_context *); + +#define CBOR_PARSE_IF(_ctx, _type, _target) if (((_ctx)->type == CBOR_##_type) && CBOR_STORE_##_type((_ctx), _target)) +#define CBOR_PARSE_ONLY(_ctx, _type, _target) CBOR_PARSE_IF(_ctx, _type, _target) {} else CBOR_PARSER_ERROR("Expected %s for %s, got %s", #_type, #_target, cbor_type_str((_ctx)->type)) + +#define CBOR_STORE_POSINT(_ctx, _target) ((_target = (_ctx)->value), 1) +#define CBOR_STORE_NEGINT(_ctx, _target) ((_target = -1LL-(_ctx)->value), 1) +#define CBOR_STORE_BYTES(_ctx, _target) ({ \ + if ((_ctx)->tflags & CPT_VARLEN) CBOR_PARSER_ERROR("Variable length string not supported yet"); \ + if ((_target)) CBOR_PARSER_ERROR("Duplicate argument %s", #_target); \ + ASSERT_DIE(!(_ctx)->target_buf); \ + _target = (_ctx)->target_buf = lp_alloc((_ctx)->lp, ((_ctx)->target_len = (_ctx)->value) + 1); \ + 1; }) +#define CBOR_STORE_TEXT CBOR_STORE_BYTES + + #endif