]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Lib: added a generic CBOR parser framework
authorMaria Matejka <mq@ucw.cz>
Thu, 3 Oct 2024 21:02:34 +0000 (23:02 +0200)
committerMaria Matejka <mq@ucw.cz>
Sun, 23 Feb 2025 18:01:48 +0000 (19:01 +0100)
lib/Makefile
lib/cbor-parser.c [new file with mode: 0644]
lib/cbor.c
lib/cbor.h

index 77866908c0e94066f39041e757342e46570379f7..c542b293980acb872fdb2b9bdb6b0f155dc4273e 100644 (file)
@@ -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 (file)
index 0000000..caa3c72
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ *     BIRD CBOR parser
+ *
+ *     (c) 2024 Maria Matejka <mq@jmq.cz>
+ *
+ *     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;
+}
index a50319eca4b6126aa6d02496b758d68a420ae3da..cdc1be2a2bd198d829bd2878c8a0c86dffe4a4f3 100644 (file)
@@ -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);
index 36c16f68ab17352e8a684da9f1ad8bff4d02915d..f71ca9fbf7b49b0f86e5d1a53a4d5ff2120292e5 100644 (file)
@@ -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