--- /dev/null
+/*
+ * 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;
+}
#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
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