Mercurial > libjeffpc
changeset 684:41a3427cde1d
cbor: unpacking API
This is an API to decode specific CBOR items.
Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author | Josef 'Jeff' Sipek <jeffpc@josefsipek.net> |
---|---|
date | Mon, 06 Nov 2017 11:15:00 -0400 |
parents | b3a4ac14a22d |
children | 09e338f3ebb2 |
files | fmt_cbor.c include/jeffpc/cbor.h mapfile-vers tests/.hgignore tests/CMakeLists.txt tests/test_cbor_unpack.c |
diffstat | 6 files changed, 862 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/fmt_cbor.c Thu Oct 11 14:20:08 2018 -0400 +++ b/fmt_cbor.c Mon Nov 06 11:15:00 2017 -0400 @@ -20,6 +20,7 @@ * SOFTWARE. */ +#include <jeffpc/mem.h> #include <jeffpc/cbor.h> #include <jeffpc/nvl.h> @@ -359,3 +360,416 @@ return 0; } + +/* + * unpack + */ + +static int read_cbor_type(struct buffer *buffer, enum major_type *type, + uint8_t *extra) +{ + uint8_t byte; + ssize_t ret; + + ret = buffer_read(buffer, &byte, 1); + if (ret < 1) + return ret ? ret : -EINVAL; + + *type = byte >> 5; + *extra = byte & 0x1f; + + return 0; +} + +static int get_addl_bytes(struct buffer *buffer, uint8_t extra, uint64_t *out) +{ + const void *ptr; + ssize_t ret; + size_t size; + + switch (extra) { + case ADDL_UINT8: + size = 1; + break; + case ADDL_UINT16: + size = 2; + break; + case ADDL_UINT32: + size = 4; + break; + case ADDL_UINT64: + size = 8; + break; + default: + if (extra > 23) + return -EINVAL; + + size = 0; + break; + } + + if (buffer_remain(buffer) < size) + return -EINVAL; + + ptr = buffer_data_current(buffer); + + switch (size) { + case 0: + *out = extra; + break; + case 1: + *out = be8_to_cpu_unaligned(ptr); + break; + case 2: + *out = be16_to_cpu_unaligned(ptr); + break; + case 4: + *out = be32_to_cpu_unaligned(ptr); + break; + case 8: + *out = be64_to_cpu_unaligned(ptr); + break; + } + + ret = buffer_seek(buffer, size, SEEK_CUR); + + return (ret < 0) ? ret : 0; +} + +static int unpack_cbor_int(struct buffer *buffer, enum major_type expected_type, + uint64_t *out) +{ + enum major_type type; + uint8_t extra; + int ret; + + ret = read_cbor_type(buffer, &type, &extra); + if (ret) + return ret; + + if (expected_type != type) + return -EILSEQ; + + return get_addl_bytes(buffer, extra, out); +} + +/* NOTE: the FLOAT major type is used for a *lot* of different things */ +static int unpack_cbor_float(struct buffer *buffer, uint8_t *extra) +{ + enum major_type type; + int ret; + + ret = read_cbor_type(buffer, &type, extra); + if (ret) + return ret; + + if (type != CMT_FLOAT) + return -EILSEQ; + + switch (*extra) { + case ADDL_FLOAT_FALSE: + case ADDL_FLOAT_TRUE: + case ADDL_FLOAT_NULL: + case ADDL_FLOAT_BREAK: + return 0; + } + + return -EILSEQ; +} + +static int unpack_cbor_arraymap_start(struct buffer *buffer, + enum major_type exp_type, uint8_t indef, + uint64_t *len, bool *end_required) +{ + enum major_type type; + uint8_t extra; + int ret; + + ret = read_cbor_type(buffer, &type, &extra); + if (ret) + return ret; + + if (type != exp_type) + return -EILSEQ; + + if (extra == indef) { + *end_required = true; + *len = 0; + return 0; + } else { + *end_required = false; + return get_addl_bytes(buffer, extra, len); + } +} + +static int sync_buffers(struct buffer *orig, struct buffer *tmp) +{ + ssize_t ret; + + ret = buffer_seek(orig, buffer_used(tmp), SEEK_CUR); + + return (ret < 0) ? ret : 0; +} + +int cbor_unpack_uint(struct buffer *buffer, uint64_t *v) +{ + struct buffer tmp; + int ret; + + buffer_init_static(&tmp, buffer_data_current(buffer), + buffer_remain(buffer), false); + + ret = unpack_cbor_int(&tmp, CMT_UINT, v); + if (ret) + return ret; + + return sync_buffers(buffer, &tmp); +} + +int cbor_unpack_nint(struct buffer *buffer, uint64_t *v) +{ + struct buffer tmp; + int ret; + + buffer_init_static(&tmp, buffer_data_current(buffer), + buffer_remain(buffer), false); + + ret = unpack_cbor_int(&tmp, CMT_NINT, v); + if (ret) + return ret; + + return sync_buffers(buffer, &tmp); +} + +int cbor_unpack_int(struct buffer *buffer, int64_t *v) +{ + struct buffer tmp; + uint64_t tmpv; + int ret; + + /* + * First, try unsigned ints + */ + + buffer_init_static(&tmp, buffer_data_current(buffer), + buffer_remain(buffer), false); + + ret = cbor_unpack_uint(&tmp, &tmpv); + if (!ret) { + if (tmpv > INT64_MAX) + return -EOVERFLOW; + + *v = tmpv; + + return sync_buffers(buffer, &tmp); + } + + /* + * Second, try negative ints + */ + + buffer_init_static(&tmp, buffer_data_current(buffer), + buffer_remain(buffer), false); + + ret = cbor_unpack_nint(&tmp, &tmpv); + if (!ret) { + /* 2's complement has one extra negative number */ + if (tmpv > (((uint64_t) INT64_MAX) + 1)) + return -EOVERFLOW; + + *v = ((~tmpv) + 1); + + return sync_buffers(buffer, &tmp); + } + + return ret; +} + +int cbor_unpack_blob(struct buffer *buffer, const void **data, + size_t *size) +{ + return -ENOTSUP; +} + +int cbor_unpack_cstr_len(struct buffer *buffer, char **str, size_t *len) +{ + uint64_t parsed_len; + struct buffer tmp; + ssize_t ret; + char *out; + + buffer_init_static(&tmp, buffer_data_current(buffer), + buffer_remain(buffer), false); + + ret = unpack_cbor_int(&tmp, CMT_TEXT, &parsed_len); + if (ret) + return ret; + + /* can't handle strings longer than what fits in memory */ + if (parsed_len > SIZE_MAX) + return -EOVERFLOW; + + out = malloc(parsed_len + 1); + if (!out) + return -ENOMEM; + + ret = buffer_read(&tmp, out, parsed_len); + if (ret < 0) + goto err; + + /* must read exactly the number of bytes */ + if (ret != parsed_len) { + ret = -EILSEQ; + goto err; + } + + out[parsed_len] = '\0'; + + *len = parsed_len; + *str = out; + + return sync_buffers(buffer, &tmp); + +err: + free(out); + + return ret; +} + +int cbor_unpack_str(struct buffer *buffer, struct str **str) +{ + char *s; + size_t len; + int ret; + + ret = cbor_unpack_cstr_len(buffer, &s, &len); + if (ret) + return ret; + + *str = str_alloc(s); + + return IS_ERR(*str) ? PTR_ERR(*str) : 0; +} + +int cbor_unpack_bool(struct buffer *buffer, bool *b) +{ + struct buffer tmp; + uint8_t extra; + int ret; + + buffer_init_static(&tmp, buffer_data_current(buffer), + buffer_remain(buffer), false); + + ret = unpack_cbor_float(&tmp, &extra); + if (ret) + return ret; + + switch (extra) { + case ADDL_FLOAT_FALSE: + *b = false; + break; + case ADDL_FLOAT_TRUE: + *b = true; + break; + default: + return -EILSEQ; + } + + return sync_buffers(buffer, &tmp); +} + +int cbor_unpack_null(struct buffer *buffer) +{ + struct buffer tmp; + uint8_t extra; + int ret; + + buffer_init_static(&tmp, buffer_data_current(buffer), + buffer_remain(buffer), false); + + ret = unpack_cbor_float(&tmp, &extra); + if (ret) + return ret; + + switch (extra) { + case ADDL_FLOAT_NULL: + break; + default: + return -EILSEQ; + } + + return sync_buffers(buffer, &tmp); +} + +int cbor_unpack_break(struct buffer *buffer) +{ + struct buffer tmp; + uint8_t extra; + int ret; + + buffer_init_static(&tmp, buffer_data_current(buffer), + buffer_remain(buffer), false); + + ret = unpack_cbor_float(&tmp, &extra); + if (ret) + return ret; + + switch (extra) { + case ADDL_FLOAT_BREAK: + break; + default: + return -EILSEQ; + } + + return sync_buffers(buffer, &tmp); +} + +int cbor_unpack_map_start(struct buffer *buffer, uint64_t *npairs, + bool *end_required) +{ + struct buffer tmp; + int ret; + + buffer_init_static(&tmp, buffer_data_current(buffer), + buffer_remain(buffer), false); + + ret = unpack_cbor_arraymap_start(&tmp, CMT_MAP, ADDL_MAP_INDEF, + npairs, end_required); + if (ret) + return ret; + + return sync_buffers(buffer, &tmp); +} + +int cbor_unpack_map_end(struct buffer *buffer, bool end_required) +{ + if (!end_required) + return 0; + + return cbor_unpack_break(buffer); +} + +int cbor_unpack_array_start(struct buffer *buffer, uint64_t *nelem, + bool *end_required) +{ + struct buffer tmp; + int ret; + + buffer_init_static(&tmp, buffer_data_current(buffer), + buffer_remain(buffer), false); + + ret = unpack_cbor_arraymap_start(&tmp, CMT_ARRAY, ADDL_ARRAY_INDEF, + nelem, end_required); + if (ret) + return ret; + + return sync_buffers(buffer, &tmp); +} + +int cbor_unpack_array_end(struct buffer *buffer, bool end_required) +{ + if (!end_required) + return 0; + + return cbor_unpack_break(buffer); +}
--- a/include/jeffpc/cbor.h Thu Oct 11 14:20:08 2018 -0400 +++ b/include/jeffpc/cbor.h Mon Nov 06 11:15:00 2017 -0400 @@ -58,4 +58,26 @@ extern int cbor_peek_type(struct buffer *buffer, enum val_type *type); +/* + * On failure, the buffer state is unchanged. On success, the buffer is + * updated to point to the first byte of the next data item. + */ +extern int cbor_unpack_uint(struct buffer *buffer, uint64_t *v); +extern int cbor_unpack_nint(struct buffer *buffer, uint64_t *v); +extern int cbor_unpack_int(struct buffer *buffer, int64_t *v); +extern int cbor_unpack_blob(struct buffer *buffer, const void **data, + size_t *size); +extern int cbor_unpack_cstr_len(struct buffer *buffer, char **str, + size_t *len); +extern int cbor_unpack_str(struct buffer *buffer, struct str **str); +extern int cbor_unpack_bool(struct buffer *buffer, bool *b); +extern int cbor_unpack_null(struct buffer *buffer); +extern int cbor_unpack_break(struct buffer *buffer); +extern int cbor_unpack_map_start(struct buffer *buffer, uint64_t *npairs, + bool *end_required); +extern int cbor_unpack_map_end(struct buffer *buffer, bool end_required); +extern int cbor_unpack_array_start(struct buffer *buffer, uint64_t *nelem, + bool *end_required); +extern int cbor_unpack_array_end(struct buffer *buffer, bool end_required); + #endif
--- a/mapfile-vers Thu Oct 11 14:20:08 2018 -0400 +++ b/mapfile-vers Mon Nov 06 11:15:00 2017 -0400 @@ -65,6 +65,19 @@ cbor_pack_uint; cbor_pack_val; cbor_peek_type; + cbor_unpack_array_end; + cbor_unpack_array_start; + cbor_unpack_blob; + cbor_unpack_break; + cbor_unpack_bool; + cbor_unpack_cstr_len; + cbor_unpack_int; + cbor_unpack_map_end; + cbor_unpack_map_start; + cbor_unpack_nint; + cbor_unpack_null; + cbor_unpack_str; + cbor_unpack_uint; # cstr strcpy_safe;
--- a/tests/.hgignore Thu Oct 11 14:20:08 2018 -0400 +++ b/tests/.hgignore Mon Nov 06 11:15:00 2017 -0400 @@ -7,6 +7,7 @@ test_buffer test_cbor_pack test_cbor_peek_type +test_cbor_unpack test_container_of test_endian test_errno
--- a/tests/CMakeLists.txt Thu Oct 11 14:20:08 2018 -0400 +++ b/tests/CMakeLists.txt Mon Nov 06 11:15:00 2017 -0400 @@ -23,6 +23,7 @@ build_perf_bin(tree) build_test_bin_and_run_files(cbor_pack cbor-common/valid/*.lisp) +build_test_bin_and_run_files(cbor_unpack cbor-common/valid/*.cbor) build_test_bin_and_run_files(nvl_pack nvl-pack/*.lisp) build_test_bin_and_run_files(qstring qstring-basic/*.qs) build_test_bin_and_run_files(sexpr_parser sexpr-parser/*.lisp)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_cbor_unpack.c Mon Nov 06 11:15:00 2017 -0400 @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2018-2019 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> + * + * 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. + */ + +#include <jeffpc/hexdump.h> +#include <jeffpc/buffer.h> +#include <jeffpc/val.h> +#include <jeffpc/sexpr.h> +#include <jeffpc/io.h> +#include <jeffpc/cbor.h> + +#include "test.c" + +static inline void dumpbuf(struct buffer *buf) +{ + const size_t len = buffer_used(buf); + char tmp[len * 2 + 1]; + + hexdumpz(tmp, buffer_data(buf), len, false); + fprintf(stderr, "%s", tmp); +} + +#define RUN_ONE(fxn, exp_ret, alloc, in, exp) \ + do { \ + struct buffer tmp; \ + struct val *wrap; \ + int ret; \ + \ + buffer_init_static(&tmp, buffer_data(in), \ + buffer_used(in), false); \ + \ + fprintf(stderr, "unpack via %s (should %s)...", \ + #fxn, (exp_ret) ? "fail" : "succeed"); \ + \ + ret = fxn; \ + \ + check_rets((exp_ret), ret, \ + "failed to unpack value directly"); \ + \ + if (ret) { \ + /* failed, so there is no value to compare */ \ + fprintf(stderr, "ok.\n"); \ + break; \ + } \ + \ + wrap = alloc; \ + if (!sexpr_equal(wrap, val_getref(exp))) \ + fail("not equal"); \ + \ + /* TODO: unpack via cbor_unpack_val & compare value */ \ + fprintf(stderr, "ok.\n"); \ + } while (0) + +#define VINT(u) VAL_ALLOC_INT(u) +#define VSTR(s) VAL_ALLOC_STR_STATIC(s) +#define VBOOL(b) VAL_ALLOC_BOOL(b) +#define VNULL() VAL_ALLOC_NULL() + +#define VSTRCAST(s) str_getref_val(s) + +static void check_null(struct buffer *in, struct val *exp) +{ + uint64_t nelem, npairs; + bool end_required; + struct str *str; + uint64_t u; + int64_t i; + size_t ss; + char *s; + bool b; + + RUN_ONE(cbor_unpack_uint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_nint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_int(&tmp, &i), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_cstr_len(&tmp, &s, &ss), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_str(&tmp, &str),-EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_bool(&tmp, &b), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_null(&tmp), 0, VNULL(), in, exp); + + /* only need to check the starts & forced ends */ + RUN_ONE(cbor_unpack_array_start(in, &nelem, &end_required), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_array_end(in, true), + -EILSEQ, NULL, in, exp); + + /* only need to check the starts & forced ends */ + RUN_ONE(cbor_unpack_map_start(in, &npairs, &end_required), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_map_end(in, true), + -EILSEQ, NULL, in, exp); +} + +static void check_bool(struct buffer *in, struct val *exp) +{ + uint64_t nelem, npairs; + bool end_required; + struct str *str; + uint64_t u; + int64_t i; + size_t ss; + char *s; + bool b; + + RUN_ONE(cbor_unpack_uint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_nint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_int(&tmp, &i), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_cstr_len(&tmp, &s, &ss), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_str(&tmp, &str),-EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_bool(&tmp, &b), 0, VBOOL(b), in, exp); + RUN_ONE(cbor_unpack_null(&tmp), -EILSEQ, NULL, in, exp); + + /* only need to check the starts & forced ends */ + RUN_ONE(cbor_unpack_array_start(in, &nelem, &end_required), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_array_end(in, true), + -EILSEQ, NULL, in, exp); + + /* only need to check the starts & forced ends */ + RUN_ONE(cbor_unpack_map_start(in, &npairs, &end_required), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_map_end(in, true), + -EILSEQ, NULL, in, exp); +} + +static void check_int(struct buffer *in, struct val *exp) +{ + const int int_ret = (exp->i > INT64_MAX) ? -EOVERFLOW : 0; + uint64_t nelem, npairs; + bool end_required; + struct str *str; + uint64_t u; + int64_t i; + size_t ss; + char *s; + bool b; + + RUN_ONE(cbor_unpack_uint(&tmp, &u), 0, VINT(u), in, exp); + RUN_ONE(cbor_unpack_nint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_int(&tmp, &i), int_ret, VINT(i), in, exp); + RUN_ONE(cbor_unpack_cstr_len(&tmp, &s, &ss), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_str(&tmp, &str),-EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_bool(&tmp, &b), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_null(&tmp), -EILSEQ, NULL, in, exp); + + /* only need to check the starts & forced ends */ + RUN_ONE(cbor_unpack_array_start(in, &nelem, &end_required), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_array_end(in, true), + -EILSEQ, NULL, in, exp); + + /* only need to check the starts & forced ends */ + RUN_ONE(cbor_unpack_map_start(in, &npairs, &end_required), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_map_end(in, true), + -EILSEQ, NULL, in, exp); +} + +static void check_str(struct buffer *in, struct val *exp) +{ + uint64_t nelem, npairs; + bool end_required; + struct str *str; + uint64_t u; + int64_t i; + size_t ss; + char *s; + bool b; + + RUN_ONE(cbor_unpack_uint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_nint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_int(&tmp, &i), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_cstr_len(&tmp, &s, &ss), + 0, VSTR(s), in, exp); + RUN_ONE(cbor_unpack_str(&tmp, &str),0, VSTRCAST(str), in, exp); + RUN_ONE(cbor_unpack_bool(&tmp, &b), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_null(&tmp), -EILSEQ, NULL, in, exp); + + /* only need to check the starts & forced ends */ + RUN_ONE(cbor_unpack_array_start(in, &nelem, &end_required), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_array_end(in, true), + -EILSEQ, NULL, in, exp); + + /* only need to check the starts & forced ends */ + RUN_ONE(cbor_unpack_map_start(in, &npairs, &end_required), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_map_end(in, true), + -EILSEQ, NULL, in, exp); +} + +static void check_arr(struct buffer *in, struct val *exp) +{ + uint64_t npairs; + bool end_required; + struct str *str; + uint64_t u; + int64_t i; + size_t ss; + char *s; + bool b; + + RUN_ONE(cbor_unpack_uint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_nint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_int(&tmp, &i), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_cstr_len(&tmp, &s, &ss), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_str(&tmp, &str),-EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_bool(&tmp, &b), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_null(&tmp), -EILSEQ, NULL, in, exp); + +#if 0 + /* FIXME: RUN_ONE resets the buffer state */ + RUN_ONE(cbor_unpack_array_start(in, &npairs, &end_required), + 0, X, in, exp); + /* TODO: unpack vals in a loop */ + RUN_ONE(cbor_unpack_array_end(in, end_required), + 0, X, in, exp); +#endif + + /* only need to check the starts & forced ends */ + RUN_ONE(cbor_unpack_map_start(in, &npairs, &end_required), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_map_end(in, true), + -EILSEQ, NULL, in, exp); +} + +static void check_nvl(struct buffer *in, struct val *exp) +{ + uint64_t nelem; + bool end_required; + struct str *str; + uint64_t u; + int64_t i; + size_t ss; + char *s; + bool b; + + RUN_ONE(cbor_unpack_uint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_nint(&tmp, &u), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_int(&tmp, &i), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_cstr_len(&tmp, &s, &ss), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_str(&tmp, &str),-EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_bool(&tmp, &b), -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_null(&tmp), -EILSEQ, NULL, in, exp); + + /* only need to check the starts & forced ends */ + RUN_ONE(cbor_unpack_array_start(in, &nelem, &end_required), + -EILSEQ, NULL, in, exp); + RUN_ONE(cbor_unpack_array_end(in, true), + -EILSEQ, NULL, in, exp); + +#if 0 + /* FIXME: RUN_ONE resets the buffer state */ + RUN_ONE(cbor_unpack_map_start(in, &npairs, &end_required), + 0, X, in, exp); + /* TODO: unpack vals in a loop */ + RUN_ONE(cbor_unpack_map_end(in, end_required), + 0, X, in, exp); +#endif +} + +static void check_cons(struct buffer *in, struct val *exp) +{ + enum val_type type; + int ret; + + if (!sexpr_is_null(exp)) + fail("non-empty cons is not supported"); + + ret = cbor_peek_type(in, &type); + check_rets(0, ret, "failed to peek into input buffer"); + + val_putref(exp); + + switch (type) { + case VT_ARRAY: + check_arr(in, VAL_ALLOC_ARRAY_STATIC(NULL, 0)); + break; + case VT_NVL: + check_nvl(in, VAL_ALLOC_NVL()); + break; + case VT_NULL: + case VT_INT: + case VT_STR: + case VT_SYM: + case VT_BOOL: + case VT_CONS: + case VT_CHAR: + case VT_BLOB: + fail("peeked type doesn't match empty cons"); + } +} + +static void onefile(struct buffer *in, struct val *expected) +{ + struct val *orig_expected = expected; + + fprintf(stderr, "input: "); + dumpbuf(in); + fprintf(stderr, "\n"); + fprintf(stderr, "expect:\n"); + val_dump(expected, 1); + + expected = sexpr_compact(expected); + ASSERT(!IS_ERR(expected)); + + if (expected != orig_expected) { + fprintf(stderr, "expect (compacted):\n"); + val_dump(expected, 1); + } + + switch (expected->type) { + case VT_NULL: + check_null(in, expected); + break; + case VT_INT: + check_int(in, expected); + break; + case VT_BOOL: + check_bool(in, expected); + break; + case VT_STR: + check_str(in, expected); + break; + case VT_ARRAY: + check_arr(in, expected); + break; + case VT_NVL: + check_nvl(in, expected); + break; + case VT_CONS: + /* + * Empty nvlists and empty arrays look the same when + * serialized as an sexpr. Therefore, we need to + * peek at the input buffer to know what to expect. + */ + check_cons(in, expected); + break; + case VT_SYM: + case VT_CHAR: + case VT_BLOB: + fail("Unsupported val type"); + break; + } + + val_putref(expected); +} + +static struct val *get_expected_output(const char *fname) +{ + char expfname[FILENAME_MAX]; + struct val *lv; + size_t len; + char *tmp; + + VERIFY3U(strlen("lisp"), <=, strlen("cbor")); + + /* replace .cbor with .lisp */ + strcpy(expfname, fname); + strcpy(expfname + strlen(expfname) - 4, "lisp"); + + tmp = read_file_len(expfname, &len); + ASSERT(!IS_ERR(tmp)); + + lv = sexpr_parse(tmp, len); + if (IS_ERR(lv)) + fail("failed to parse input: %s", xstrerror(PTR_ERR(lv))); + + free(tmp); + + return lv; +} + +static void test(const char *fname) +{ + struct buffer input; + size_t len; + char *in; + + in = read_file_len(fname, &len); + if (IS_ERR(in)) + fail("failed to read input (%s)", xstrerror(PTR_ERR(in))); + + buffer_init_static(&input, in, len, false); + + onefile(&input, get_expected_output(fname)); + + free((void *) buffer_data(&input)); +}