diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index a39e4cea..1506cd68 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -22,7 +22,10 @@ `zcbor_bool_*()`, `zcbor_nil_*()`, and `zcbor_undefined_*()`. If a removed variant is strictly needed, add your own forward declaration in your code. -* Code generation naming: +* Code generation: + + * `zcbor_entry_function()` gained a new argument. + Regenerate or manually edit your generated files to add a 0 parameter to all calls to `zcbor_entry_function()`. * More C keywords are now capitalized to avoid naming collision. You might have to capitalize some instances if your code was generated to have those names. diff --git a/README.md b/README.md index 713ad357..81bb950f 100644 --- a/README.md +++ b/README.md @@ -442,7 +442,7 @@ usage: zcbor code [-h] -c CDDL [--no-prelude] [-v] ENTRY_TYPES [ENTRY_TYPES ...] [-d] [-e] [--time-header] [--git-sha-header] [-b {32,64}] [--include-prefix INCLUDE_PREFIX] [-s] - [--file-header FILE_HEADER] + [--file-header FILE_HEADER] [--unordered-maps] Parse a CDDL file and produce C code that validates and xcodes CBOR. The output from this script is a C file and a header file. The header file @@ -543,6 +543,13 @@ options: generated files, e.g. copyright. Can be a string or a path to a file. If interpreted as a path to an existing file, the file's contents will be used. + --unordered-maps Add support in the generated code for parsing maps + with unknown element order. When enabled, the + generated code will use the zcbor_unordered_map_*() + API to decode data whenever inside a map. zcbor + detects when ZCBOR_MAP_SMART_SEARCH is needed and + enable This places restrictions on the level of + ambiguity allowed between map keys in a map. ``` diff --git a/include/zcbor_common.h b/include/zcbor_common.h index b2b7338e..e28d7822 100644 --- a/include/zcbor_common.h +++ b/include/zcbor_common.h @@ -342,7 +342,7 @@ void zcbor_new_state(zcbor_state_t *state_array, size_t n_states, */ int zcbor_entry_function(const uint8_t *payload, size_t payload_len, void *result, size_t *payload_len_out, zcbor_state_t *state, zcbor_decoder_t func, - size_t n_states, size_t elem_count); + size_t n_states, size_t elem_count, size_t n_flags); #ifdef ZCBOR_STOP_ON_ERROR /** Check stored error and fail if present, but only if stop_on_error is true. @@ -530,7 +530,7 @@ static inline size_t zcbor_flags_to_states(size_t num_flags) #define ZCBOR_FLAG_STATES(n_flags) zcbor_flags_to_states(n_flags) #else -#define ZCBOR_FLAG_STATES(n_flags) 0 +#define ZCBOR_FLAG_STATES(n_flags) (n_flags * 0) #endif size_t strnlen(const char *, size_t); diff --git a/samples/pet/pet.cmake b/samples/pet/pet.cmake index 030f96d3..294bac8a 100644 --- a/samples/pet/pet.cmake +++ b/samples/pet/pet.cmake @@ -21,3 +21,4 @@ target_include_directories(pet PUBLIC ${CMAKE_CURRENT_LIST_DIR}/../../include ${CMAKE_CURRENT_LIST_DIR}/include ) +target_compile_definitions(pet PUBLIC ZCBOR_MAP_SMART_SEARCH) diff --git a/samples/pet/src/pet_decode.c b/samples/pet/src/pet_decode.c index b060958f..3a68c781 100644 --- a/samples/pet/src/pet_decode.c +++ b/samples/pet/src/pet_decode.c @@ -20,6 +20,7 @@ #error "The type file was generated with a different default_max_qty than this file" #endif + #define log_result(state, result, func) do { \ if (!result) { \ zcbor_trace_file(state); \ @@ -62,8 +63,8 @@ int cbor_decode_Pet( struct Pet *result, size_t *payload_len_out) { - zcbor_state_t states[4]; + zcbor_state_t states[4 + 0]; return zcbor_entry_function(payload, payload_len, (void *)result, payload_len_out, states, - (zcbor_decoder_t *)decode_Pet, sizeof(states) / sizeof(zcbor_state_t), 1); + (zcbor_decoder_t *)decode_Pet, sizeof(states) / sizeof(zcbor_state_t), 1, 0); } diff --git a/samples/pet/src/pet_encode.c b/samples/pet/src/pet_encode.c index 4f390c29..b9507284 100644 --- a/samples/pet/src/pet_encode.c +++ b/samples/pet/src/pet_encode.c @@ -20,6 +20,7 @@ #error "The type file was generated with a different default_max_qty than this file" #endif + #define log_result(state, result, func) do { \ if (!result) { \ zcbor_trace_file(state); \ @@ -56,8 +57,8 @@ int cbor_encode_Pet( const struct Pet *input, size_t *payload_len_out) { - zcbor_state_t states[4]; + zcbor_state_t states[4 + 0]; return zcbor_entry_function(payload, payload_len, (void *)input, payload_len_out, states, - (zcbor_decoder_t *)encode_Pet, sizeof(states) / sizeof(zcbor_state_t), 1); + (zcbor_decoder_t *)encode_Pet, sizeof(states) / sizeof(zcbor_state_t), 1, 0); } diff --git a/src/zcbor_common.c b/src/zcbor_common.c index 5bf959e6..76e6636a 100644 --- a/src/zcbor_common.c +++ b/src/zcbor_common.c @@ -301,9 +301,13 @@ size_t zcbor_remaining_str_len(zcbor_state_t *state) int zcbor_entry_function(const uint8_t *payload, size_t payload_len, void *result, size_t *payload_len_out, zcbor_state_t *state, zcbor_decoder_t func, - size_t n_states, size_t elem_count) + size_t n_states, size_t elem_count, size_t n_flags) { - zcbor_new_state(state, n_states, payload, payload_len, elem_count, NULL, 0); + zcbor_new_state(state, n_states, payload, payload_len, elem_count, + (uint8_t *)&state[n_states - 1 - ZCBOR_FLAG_STATES(n_flags)], + ZCBOR_FLAG_STATES(n_flags) * sizeof(zcbor_state_t)); + + state->constant_state->manually_process_elem = true; bool ret = func(state, result); diff --git a/src/zcbor_decode.c b/src/zcbor_decode.c index 1ced3e0b..0e65a000 100644 --- a/src/zcbor_decode.c +++ b/src/zcbor_decode.c @@ -959,6 +959,10 @@ bool zcbor_unordered_map_search(zcbor_decoder_t key_decoder, zcbor_state_t *stat (void)old_flags; } + if (!should_try_key(state)) { + zcbor_log("Skipping element at index %zu.\n", get_current_index(state, 0)); + } + if (should_try_key(state) && try_key(state, key_result, key_decoder)) { if (!ZCBOR_MANUALLY_PROCESS_ELEM(state)) { ZCBOR_FAIL_IF(!zcbor_elem_processed(state)); @@ -1576,8 +1580,8 @@ bool zcbor_multi_decode(size_t min_decode, *num_decode = i; state->payload = payload_bak; state->elem_count = elem_count_bak; - ZCBOR_ERR_IF(i < min_decode, ZCBOR_ERR_ITERATIONS); zcbor_log("Found %zu elements.\r\n", i); + ZCBOR_ERR_IF(i < min_decode, ZCBOR_ERR_ITERATIONS); return true; } } diff --git a/tests/cases/serial_recovery.cddl b/tests/cases/serial_recovery.cddl index a8211a77..d25b6b47 100644 --- a/tests/cases/serial_recovery.cddl +++ b/tests/cases/serial_recovery.cddl @@ -4,13 +4,11 @@ ; SPDX-License-Identifier: Apache-2.0 ; -Member = ("image" => int) / - ("data" => bstr) / - ("len" => int) / - ("off" => int) / - ("sha" => bstr) / - (tstr => any) - Upload = { - 3*8members: Member + ? "image" => uint, + ? "len" => uint, + "off" => uint, + ? "sha" => bstr, + ? "data" => bstr, + ? "upgrade" => bool, } diff --git a/tests/cases/unordered_map.cddl b/tests/cases/unordered_map.cddl new file mode 100644 index 00000000..f53db45f --- /dev/null +++ b/tests/cases/unordered_map.cddl @@ -0,0 +1,46 @@ +; +; Copyright (c) 2023 Nordic Semiconductor ASA +; +; SPDX-License-Identifier: Apache-2.0 +; + +UnorderedMap1 = { + 1 => "one", + 2 => "two", + int => bstr, + "foo" => "bar", + bazboz, + *bstr => intlist, +} + +bazboz = ("baz" => "boz") +intlist = [*int] + +UnorderedMap2 = { + +typeUnion, + "map" => { + ?-1 => bstr, + ?-2 => bstr, + ?bool => int, + } +} + +typeUnion = type1 / type2 / typeDefault +type1 = 1 => tstr +type2 = 2 => tstr +typeDefault = int => nil + +UnorderedMap3 = { + 1*2000 uint => uint, + 1*2000 nint => nint, +} + +group1 = ( + uint => tstr, + nint => bstr, +) +type3 = 3*3group1 + +UnorderedMap4 = { + 3*3group1, +} diff --git a/tests/decode/test3_simple/CMakeLists.txt b/tests/decode/test3_simple/CMakeLists.txt index 6621eef1..dc19f5ef 100644 --- a/tests/decode/test3_simple/CMakeLists.txt +++ b/tests/decode/test3_simple/CMakeLists.txt @@ -35,6 +35,7 @@ set(py_command_serial_recovery -d ${bit_arg} --short-names + --unordered-maps # Testing the --include-prefix option --include-prefix serial diff --git a/tests/decode/test3_simple/src/main.c b/tests/decode/test3_simple/src/main.c index 0dd120ac..60ddd8db 100644 --- a/tests/decode/test3_simple/src/main.c +++ b/tests/decode/test3_simple/src/main.c @@ -487,19 +487,15 @@ ZTEST(cbor_decode_test3, test_serial1) zassert_equal(ZCBOR_SUCCESS, ret, "decoding failed: %d.", ret); zassert_equal(sizeof(serial_rec_input1), decode_len, NULL); - zassert_equal(5, upload.members_count, - "expect 5 members"); - zassert_equal(Member_data_c, upload.members[0].members - .Member_choice, "expect data 1st"); - zassert_equal(Member_image_c, upload.members[1].members - .Member_choice, "expect image 2nd"); - zassert_equal(Member_len_c, upload.members[2].members - .Member_choice, "was %d\r\n", upload.members[2].members - .Member_choice); - zassert_equal(Member_off_c, upload.members[3].members - .Member_choice, "expect off 4th"); - zassert_equal(Member_sha_c, upload.members[4].members - .Member_choice, "expect sha 5th"); + zassert_true(upload.data_present, NULL); + zassert_equal(0x129, upload.data.data.len, NULL); + zassert_true(upload.image_present, NULL); + zassert_equal(0, upload.image.image, NULL); + zassert_true(upload.len_present, NULL); + zassert_equal(0x3b2c, upload.len.len, NULL); + zassert_equal(0, upload.off, NULL); + zassert_true(upload.sha_present, NULL); + zassert_equal(0x20, upload.sha.sha.len, NULL); } ZTEST(cbor_decode_test3, test_serial2) @@ -511,18 +507,15 @@ ZTEST(cbor_decode_test3, test_serial2) zassert_equal(ZCBOR_SUCCESS, ret, "decoding failed: %d.", ret); zassert_equal(sizeof(serial_rec_input2), decode_len, NULL); - zassert_equal(5, upload.members_count, - "expect 5 members"); - zassert_equal(Member_data_c, upload.members[0].members - .Member_choice, "expect data 1st"); - zassert_equal(Member_image_c, upload.members[1].members - .Member_choice, "expect image 2nd"); - zassert_equal(Member_len_c, upload.members[2].members - .Member_choice, "expect len 3rd"); - zassert_equal(Member_off_c, upload.members[3].members - .Member_choice, "expect off 4th"); - zassert_equal(Member_sha_c, upload.members[4].members - .Member_choice, "expect sha 5th"); + zassert_true(upload.data_present, NULL); + zassert_equal(0x129, upload.data.data.len, NULL); + zassert_true(upload.image_present, NULL); + zassert_equal(0, upload.image.image, NULL); + zassert_true(upload.len_present, NULL); + zassert_equal(0x2fe0, upload.len.len, NULL); + zassert_equal(0, upload.off, NULL); + zassert_true(upload.sha_present, NULL); + zassert_equal(0x20, upload.sha.sha.len, NULL); } ZTEST_SUITE(cbor_decode_test3, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/decode/test9_manifest14/CMakeLists.txt b/tests/decode/test9_manifest14/CMakeLists.txt index c6c4cbdd..ec596d82 100644 --- a/tests/decode/test9_manifest14/CMakeLists.txt +++ b/tests/decode/test9_manifest14/CMakeLists.txt @@ -8,6 +8,7 @@ cmake_minimum_required(VERSION 3.13.1) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(test9_manifest14) +set(MAP_SEARCH_FLAGS ON) # Because of --unordered maps include(../../cmake/test_template.cmake) if (NOT MANIFEST) @@ -30,6 +31,7 @@ set(py_command SUIT_Common_Sequence -d ${bit_arg} + --unordered-maps ) execute_process( diff --git a/tests/decode/testA_unordered_map/CMakeLists.txt b/tests/decode/testA_unordered_map/CMakeLists.txt new file mode 100644 index 00000000..80160325 --- /dev/null +++ b/tests/decode/testA_unordered_map/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required(VERSION 3.13.1) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(test9_manifest14) +set(MAP_SEARCH_FLAGS ON) # Because of --unordered maps +set(MAP_SMART_SEARCH ON) +include(../../cmake/test_template.cmake) + +set(py_command + ${PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_LIST_DIR}/../../../zcbor/zcbor.py + code + -c ${CMAKE_CURRENT_LIST_DIR}/../../cases/unordered_map.cddl + --output-cmake ${PROJECT_BINARY_DIR}/unordered_map.cmake + -t + UnorderedMap1 + UnorderedMap2 + UnorderedMap3 + UnorderedMap4 + -d + ${bit_arg} + --unordered-maps + ) + +execute_process( + COMMAND + ${py_command} + COMMAND_ERROR_IS_FATAL ANY +) + +include(${PROJECT_BINARY_DIR}/unordered_map.cmake) + +target_link_libraries(unordered_map PRIVATE zephyr_interface) +target_link_libraries(app PRIVATE unordered_map) diff --git a/tests/decode/testA_unordered_map/prj.conf b/tests/decode/testA_unordered_map/prj.conf new file mode 100644 index 00000000..7cc649c9 --- /dev/null +++ b/tests/decode/testA_unordered_map/prj.conf @@ -0,0 +1,8 @@ +# +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_ZTEST=y +CONFIG_ZTEST_STACK_SIZE=131072 diff --git a/tests/decode/testA_unordered_map/src/main.c b/tests/decode/testA_unordered_map/src/main.c new file mode 100644 index 00000000..8e56d7b3 --- /dev/null +++ b/tests/decode/testA_unordered_map/src/main.c @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "unordered_map_decode.h" +#include + + + +ZTEST(cbor_decode_testA, test_unordered_map1) +{ + const uint8_t payload_unordered_map1_1[] = { + MAP(7), + 3, 0x40, + 0x41, 0, LIST(3), 1, 2, 3, END + 2, 0x63, 't', 'w', 'o', + 0x63, 'b', 'a', 'z', 0x63, 'b', 'o', 'z', + 0x41, 2, LIST(0), END + 1, 0x63, 'o', 'n', 'e', + 0x63, 'f', 'o', 'o', 0x63, 'b', 'a', 'r', + END + }; + + const uint8_t payload_unordered_map1_2[] = { + MAP(5), + 2, 0x63, 't', 'w', 'o', + 3, 0x40, + 1, 0x63, 'o', 'n', 'e', + 0x63, 'b', 'a', 'z', 0x63, 'b', 'o', 'z', + 0x63, 'f', 'o', 'o', 0x63, 'b', 'a', 'r', + END + }; + + const uint8_t payload_unordered_map1_inv3[] = { + MAP(5), + 2, 0x63, 't', 'w', 'o', + 3, 0x40, + 1, 0x63, 'o', 'n', 'f' /* here */, + 0x63, 'b', 'a', 'z', 0x63, 'b', 'o', 'z', + 0x63, 'f', 'o', 'o', 0x63, 'b', 'a', 'r', + END + }; + + const uint8_t payload_unordered_map1_inv4[] = { + MAP(6), + 3, 0x40, + 0x63, 'b', 'a', 'z', 0x63, 'b', 'o', 'z', + 0x41, 0, LIST(3), 1, 2, 0x40 /* here */, END + 0x63, 'f', 'o', 'o', 0x63, 'b', 'a', 'r', + 1, 0x63, 'o', 'n', 'e', + 2, 0x63, 't', 'w', 'o', + END + }; + + struct UnorderedMap1 unordered_map1; + + int err = cbor_decode_UnorderedMap1(payload_unordered_map1_1, sizeof(payload_unordered_map1_1), &unordered_map1, NULL); + zassert_equal(ZCBOR_SUCCESS, err, "%s %d\n", zcbor_error_str(err), err); + zassert_equal(3, unordered_map1.UnorderedMap1_intbstr_key); + zassert_equal(0, unordered_map1.UnorderedMap1_intbstr.len); + zassert_equal(2, unordered_map1.UnorderedMap1_intlist_m_count); + zassert_equal(1, unordered_map1.UnorderedMap1_intlist_m[0].UnorderedMap1_intlist_m_key.len); + zassert_equal(2, unordered_map1.UnorderedMap1_intlist_m[0].UnorderedMap1_intlist_m_key.value[0]); + zassert_equal(0, unordered_map1.UnorderedMap1_intlist_m[0].UnorderedMap1_intlist_m.intlist_int_count); + zassert_equal(1, unordered_map1.UnorderedMap1_intlist_m[1].UnorderedMap1_intlist_m_key.len); + zassert_equal(0, unordered_map1.UnorderedMap1_intlist_m[1].UnorderedMap1_intlist_m_key.value[0]); + zassert_equal(3, unordered_map1.UnorderedMap1_intlist_m[1].UnorderedMap1_intlist_m.intlist_int_count); + zassert_equal(1, unordered_map1.UnorderedMap1_intlist_m[1].UnorderedMap1_intlist_m.intlist_int[0]); + zassert_equal(2, unordered_map1.UnorderedMap1_intlist_m[1].UnorderedMap1_intlist_m.intlist_int[1]); + zassert_equal(3, unordered_map1.UnorderedMap1_intlist_m[1].UnorderedMap1_intlist_m.intlist_int[2]); + + err = cbor_decode_UnorderedMap1(payload_unordered_map1_2, sizeof(payload_unordered_map1_2), &unordered_map1, NULL); + zassert_equal(ZCBOR_SUCCESS, err, "%s %d\n", zcbor_error_str(err), err); + zassert_equal(3, unordered_map1.UnorderedMap1_intbstr_key); + zassert_equal(0, unordered_map1.UnorderedMap1_intbstr.len); + zassert_equal(0, unordered_map1.UnorderedMap1_intlist_m_count); + + err = cbor_decode_UnorderedMap1(payload_unordered_map1_inv3, sizeof(payload_unordered_map1_inv3), &unordered_map1, NULL); + zassert_equal(ZCBOR_ERR_WRONG_VALUE, err, "%s\n", zcbor_error_str(err)); + + err = cbor_decode_UnorderedMap1(payload_unordered_map1_inv4, sizeof(payload_unordered_map1_inv4), &unordered_map1, NULL); + zassert_equal(ZCBOR_ERR_ELEMS_NOT_PROCESSED, err, "%s\n", zcbor_error_str(err)); +} + + +ZTEST(cbor_decode_testA, test_unordered_map2) +{ + const uint8_t payload_unordered_map2_1[] = { + MAP(4), + 1, 0x61, 'a', + 0x63, 'm', 'a', 'p', MAP(3), + 0x20, 0x40, + 0x21, 0x40, + 0xF4, 0x18, 100, + END + 0x21, 0xF6, + 2, 0x61, 'b', + END + }; + + const uint8_t payload_unordered_map2_2[] = { + MAP(2), + 0x63, 'm', 'a', 'p', MAP(1), + 0xF5, 0x39, 0x12, 0x34, + END + 0x22, 0xF6, + END + }; + + const uint8_t payload_unordered_map2_3[] = { + MAP(3), + 2, 0x66, 'f', 'o', 'o', 'b', 'a', 'r', + 0x63, 'm', 'a', 'p', MAP(2), + 0x21, 0x40, + 0x20, 0x40, + END + 1, 0x60, + END + }; + + const uint8_t payload_unordered_map2_inv4[] = { + MAP(4), + 1, 0x61, 'a', + 0x63, 'm', 'a', 'p', MAP(3), + 0x20, 0x40, + 0x21, 0x40, + 0xF4, 0xF6 /* here */, + END + 0x21, 0xF6, + 2, 0x61, 'b', + END + }; + + const uint8_t payload_unordered_map2_inv5[] = { + MAP(4), + 1, 0x61, 'a', + 0x62, 'm', 'a', /* here */ MAP(3), + 0x20, 0x40, + 0x21, 0x40, + 0xF4, 0x18, 100, + END + 0x21, 0xF6, + 2, 0x61, 'b', + END + }; + + struct UnorderedMap2 unordered_map2; + + int err = cbor_decode_UnorderedMap2(payload_unordered_map2_1, sizeof(payload_unordered_map2_1), &unordered_map2, NULL); + zassert_equal(ZCBOR_SUCCESS, err, "%s %d\n", zcbor_error_str(err), err); + zassert_equal(3, unordered_map2.UnorderedMap2_typeUnion_m_count); + zassert_equal(typeUnion_type1_m_c, unordered_map2.UnorderedMap2_typeUnion_m[0].typeUnion_choice); + zassert_equal(typeUnion_type2_m_c, unordered_map2.UnorderedMap2_typeUnion_m[1].typeUnion_choice); + zassert_equal(typeUnion_typeDefault_m_c, unordered_map2.UnorderedMap2_typeUnion_m[2].typeUnion_choice); + zassert_equal(1, unordered_map2.UnorderedMap2_typeUnion_m[0].typeUnion_type1_m.type1.len); + zassert_equal('a', unordered_map2.UnorderedMap2_typeUnion_m[0].typeUnion_type1_m.type1.value[0]); + zassert_equal(1, unordered_map2.UnorderedMap2_typeUnion_m[1].typeUnion_type2_m.type2.len); + zassert_equal('b', unordered_map2.UnorderedMap2_typeUnion_m[1].typeUnion_type2_m.type2.value[0]); + zassert_equal(-2, unordered_map2.UnorderedMap2_typeUnion_m[2].typeUnion_typeDefault_m.typeDefault_key); + zassert_true(unordered_map2.map_nint1bstr_present); + zassert_true(unordered_map2.map_nint2bstr_present); + zassert_true(unordered_map2.map_boolint_present); + zassert_equal(0, unordered_map2.map_nint1bstr.map_nint1bstr.len); + zassert_equal(0, unordered_map2.map_nint2bstr.map_nint2bstr.len); + zassert_false(unordered_map2.map_boolint.UnorderedMap2_map_boolint_key); + zassert_equal(100, unordered_map2.map_boolint.map_boolint); + + err = cbor_decode_UnorderedMap2(payload_unordered_map2_2, sizeof(payload_unordered_map2_2), &unordered_map2, NULL); + zassert_equal(ZCBOR_SUCCESS, err, "%s %d\n", zcbor_error_str(err), err); + zassert_equal(1, unordered_map2.UnorderedMap2_typeUnion_m_count); + zassert_equal(typeUnion_typeDefault_m_c, unordered_map2.UnorderedMap2_typeUnion_m[0].typeUnion_choice); + zassert_equal(-3, unordered_map2.UnorderedMap2_typeUnion_m[0].typeUnion_typeDefault_m.typeDefault_key); + zassert_true(unordered_map2.map_boolint_present); + zassert_true(unordered_map2.map_boolint.UnorderedMap2_map_boolint_key); + zassert_equal(-0x1235, unordered_map2.map_boolint.map_boolint); + + err = cbor_decode_UnorderedMap2(payload_unordered_map2_3, sizeof(payload_unordered_map2_3), &unordered_map2, NULL); + zassert_equal(ZCBOR_SUCCESS, err, "%s %d\n", zcbor_error_str(err), err); + zassert_equal(2, unordered_map2.UnorderedMap2_typeUnion_m_count); + zassert_equal(typeUnion_type1_m_c, unordered_map2.UnorderedMap2_typeUnion_m[0].typeUnion_choice); + zassert_equal(typeUnion_type2_m_c, unordered_map2.UnorderedMap2_typeUnion_m[1].typeUnion_choice); + zassert_equal(0, unordered_map2.UnorderedMap2_typeUnion_m[0].typeUnion_type1_m.type1.len); + zassert_equal(6, unordered_map2.UnorderedMap2_typeUnion_m[1].typeUnion_type2_m.type2.len); + zassert_mem_equal("foobar", unordered_map2.UnorderedMap2_typeUnion_m[1].typeUnion_type2_m.type2.value, 6); + zassert_true(unordered_map2.map_nint1bstr_present); + zassert_true(unordered_map2.map_nint2bstr_present); + zassert_equal(0, unordered_map2.map_nint1bstr.map_nint1bstr.len); + zassert_equal(0, unordered_map2.map_nint2bstr.map_nint2bstr.len); + + err = cbor_decode_UnorderedMap2(payload_unordered_map2_inv4, sizeof(payload_unordered_map2_inv4), &unordered_map2, NULL); + zassert_equal(ZCBOR_ERR_ELEMS_NOT_PROCESSED, err, "%s %d\n", zcbor_error_str(err), err); + + err = cbor_decode_UnorderedMap2(payload_unordered_map2_inv5, sizeof(payload_unordered_map2_inv5), &unordered_map2, NULL); + zassert_equal(ZCBOR_ERR_ELEM_NOT_FOUND, err, "%s %d\n", zcbor_error_str(err), err); +} + + +ZTEST(cbor_decode_testA, test_unordered_map3) +{ + uint8_t payload_unordered_map3_1[21000]; + + ZCBOR_STATE_E(state_e, 1, payload_unordered_map3_1, sizeof(payload_unordered_map3_1), 0); + + zassert_true(zcbor_map_start_encode(state_e, 2000)); + for (uint32_t i = 1; i <= 1000; i++) { + zassert_true(zcbor_uint32_put(state_e, i), "%d\n", i); + zassert_true(zcbor_uint32_put(state_e, i), "%d\n", i); + zassert_true(zcbor_int32_put(state_e, -i), "%d\n", -i); + zassert_true(zcbor_int32_put(state_e, -i), "%d\n", -i); + } + zassert_true(zcbor_map_end_encode(state_e, 2000)); + + struct UnorderedMap3 unordered_map3; + + int err = cbor_decode_UnorderedMap3(payload_unordered_map3_1, sizeof(payload_unordered_map3_1), &unordered_map3, NULL); + zassert_equal(ZCBOR_SUCCESS, err, "%s %d\n", zcbor_error_str(err), err); + + zassert_equal(1000, unordered_map3.UnorderedMap3_uintuint_count, "%d != %d", 1000, unordered_map3.UnorderedMap3_uintuint_count); + zassert_equal(1000, unordered_map3.UnorderedMap3_nintnint_count, "%d != %d", 1000, unordered_map3.UnorderedMap3_nintnint_count); +} + + +ZTEST(cbor_decode_testA, test_unordered_map4) +{ + uint8_t payload_unordered_map4_1[50]; + + ZCBOR_STATE_E(state_e, 1, payload_unordered_map4_1, sizeof(payload_unordered_map4_1), 0); + + zassert_true(zcbor_map_start_encode(state_e, 6)); + for (int32_t i = -1; i >= -3; i--) { + zassert_true(zcbor_int32_put(state_e, i), "%d\n", i); + zassert_true(zcbor_bstr_put_lit(state_e, "world"), NULL); + } + for (uint32_t i = 1; i <= 3; i++) { + zassert_true(zcbor_uint32_put(state_e, i), "%d\n", i); + zassert_true(zcbor_tstr_put_lit(state_e, "hello"), NULL); + } + zassert_true(zcbor_map_end_encode(state_e, 6)); + + struct UnorderedMap4 unordered_map4; + + int err = cbor_decode_UnorderedMap4(payload_unordered_map4_1, sizeof(payload_unordered_map4_1), &unordered_map4, NULL); + zassert_equal(ZCBOR_SUCCESS, err, "%s %d\n", zcbor_error_str(err), err); + + zassert_equal(3, unordered_map4.UnorderedMap4_group1_m_count, "%d != %d", 3, unordered_map4.UnorderedMap4_group1_m_count); + for (uint32_t i = 1; i <= 3; i++) { + zassert_equal(i, unordered_map4.UnorderedMap4_group1_m[i - 1].UnorderedMap4_group1_m.group1_uinttstr_key); + zassert_equal(-i, unordered_map4.UnorderedMap4_group1_m[i - 1].UnorderedMap4_group1_m.group1_nintbstr_key); + } +} + + +ZTEST_SUITE(cbor_decode_testA, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/decode/testA_unordered_map/testcase.yaml b/tests/decode/testA_unordered_map/testcase.yaml new file mode 100644 index 00000000..bf977536 --- /dev/null +++ b/tests/decode/testA_unordered_map/testcase.yaml @@ -0,0 +1,4 @@ +tests: + zcbor.decode.testA_unordered_map: + platform_allow: native_posix native_posix_64 mps2_an521 qemu_malta_be + tags: zcbor decode unordered_map testA diff --git a/zcbor/zcbor.py b/zcbor/zcbor.py index ea649a01..c5a9478d 100755 --- a/zcbor/zcbor.py +++ b/zcbor/zcbor.py @@ -235,6 +235,7 @@ def __init__(self, default_max_qty, my_types, my_control_groups, base_name=None, # Key element. Only for children of "MAP" elements. self.key is of the # same class as self. self.key = None + self.is_key = False # The element specified via.cbor or.cborseq(only for byte # strings).self.cbor is of the same class as self. self.cbor = None @@ -353,7 +354,7 @@ def generate_base_name(self): if self.type == "TSTR" and self.value is not None else None) # Name an integer by its expected value: or (f"{self.type.lower()}{abs(self.value)}" - if self.type in ["INT", "UINT", "NINT"] and self.value is not None else None) + if self.type in ["UINT", "NINT"] and self.value is not None else None) # Name a type by its type name or (next((key for key, value in self.my_types.items() if value == self), None)) # Name a control group by its name @@ -510,6 +511,7 @@ def flatten(self, allow_multi=False): self.value[0].label = self.label if not self.value[0].key: self.value[0].key = self.key + self.value[0].is_key = self.is_key self.value[0].tags.extend(self.tags) return self.value elif allow_multi and self.type in ["GROUP"] and self.min_qty == 1 and self.max_qty == 1: @@ -699,6 +701,7 @@ def set_key(self, key): if key.type == "GROUP": raise TypeError("A key cannot be a group because it might represent more than 1 type.") self.key = key + key.is_key = True def set_key_or_label(self, key_or_label): """Set the self.label OR self.key of this element. @@ -939,16 +942,26 @@ def get_value(self, instr): # Return the unparsed part of the string. return instr.strip() - def elem_has_key(self): + def has_key(self): """For checking whether this element has a key (i.e. that it is a valid "MAP" child) This must have some recursion since CDDL allows the key to be hidden behind layers of indirection. """ - return self.key is not None\ - or (self.type == "OTHER" and self.my_types[self.value].elem_has_key())\ + ret = self.key is not None\ + or (self.type == "OTHER" and self.my_types[self.value].has_key())\ or (self.type in ["GROUP", "UNION"] - and (self.value and all(child.elem_has_key() for child in self.value))) + and (self.value and all(child.has_key() for child in self.value))) + return ret + + def is_valid_map_elem(self): + """For checking whether this element meets the conditions for being a valid map element. + + This can be overridden by subclasses to further validate keys. + """ + if not self.has_key(): + return False, f"Missing map key" + return True, "" def post_validate(self): """Function for performing validations that must be done after all parsing is complete. @@ -957,14 +970,12 @@ def post_validate(self): """ # Validation of this element. if self.type in ["LIST", "MAP"]: - none_keys = [child for child in self.value if not child.elem_has_key()] - child_keys = [child for child in self.value if child not in none_keys] - if self.type == "MAP" and none_keys: + invalid_elems = [child for child in self.value if not child.is_valid_map_elem()[0]] + if self.type == "MAP" and invalid_elems: raise TypeError( - "Map member(s) must have key: " + str(none_keys) + " pointing to " - + str( - [self.my_types[elem.value] for elem in none_keys - if elem.type == "OTHER"])) + "Map member(s) are invalid:\n" + '\n'.join( + [f"{str(c)}: {c.is_valid_map_elem()[1]}" for c in invalid_elems])) + child_keys = [child for child in self.value if child not in invalid_elems] if self.type == "LIST" and child_keys: raise TypeError( str(self) + linesep @@ -1045,6 +1056,7 @@ def __init__(self, *args, **kwargs): # Used as a guard against endless recursion in self.dependsOn() self.dependsOnCall = False self.skipped = False + self.unordered_maps = False def var_name(self, with_prefix=False, observe_skipped=True): """Name of variables and enum members for this element.""" @@ -1064,10 +1076,6 @@ def skip_condition(self): return True if self.type in ["LIST", "MAP", "GROUP"]: return not self.repeated_multi_var_condition() - if self.type == "OTHER": - return ((not self.repeated_multi_var_condition()) - and (not self.multi_var_condition()) - and (self.single_func_impl_condition() or self in self.my_types.values())) return False def set_skipped(self, skipped): @@ -1151,12 +1159,14 @@ def var_access(self): return "NULL" return self.access_append() - def val_access(self): + def val_access(self, top_level=False): """"Path" to access this element's actual value variable.""" if self.is_unambiguous_repeated(): ret = "NULL" elif self.skip_condition() or self.is_delegated_type(): ret = self.var_access() + elif top_level and not (self.type_def_condition() or self.repeated_type_def_condition()): + ret = self.var_access() else: ret = self.access_append(self.var_name()) return ret @@ -1273,7 +1283,9 @@ def single_func_impl_condition(self): or (self.tags and self in self.my_types.values()) or self.type_def_condition() or (self.type in ["LIST", "MAP"]) - or (self.type == "GROUP" and len(self.value) != 0)) + or (self.type == "GROUP" and len(self.value) != 0) + or (self.unordered_maps and self.is_key + and (self.repeated_single_func_impl_condition() or self.range_check_condition()))) def repeated_single_func_impl_condition(self): """Whether this element needs its own encoder/decoder function.""" @@ -1863,11 +1875,13 @@ class CddlTypes(NamedTuple): class CodeGenerator(CddlXcoder): """Class for generating C code that encode/decodes CBOR and validates it according to the CDDL. """ - def __init__(self, mode, entry_type_names, default_bit_size, *args, **kwargs): + def __init__(self, mode, entry_type_names, default_bit_size, *args, + unordered_maps=False, **kwargs): super(CodeGenerator, self).__init__(*args, **kwargs) self.mode = mode self.entry_type_names = entry_type_names self.default_bit_size = default_bit_size + self.unordered_maps = unordered_maps @classmethod def from_cddl(cddl_class, mode, *args, **kwargs): @@ -1892,6 +1906,11 @@ def is_cbor(self): def init_args(self): return (self.mode, self.entry_type_names, self.default_bit_size, self.default_max_qty) + def init_kwargs(self): + kwargs = {"unordered_maps": self.unordered_maps} + kwargs.update(super().init_kwargs()) + return kwargs + def delegate_type_condition(self): """Whether to use the C type of the first child as this type's C type""" ret = self.skip_condition() and (self.multi_var_condition() @@ -2171,11 +2190,11 @@ def type_def(self): if self.repeated_type_def_condition(): type_def_list = self.single_var_type(full=False) if type_def_list: - ret_val.extend([(self.single_var_type(full=False), self.repeated_type_name())]) + ret_val.extend([(type_def_list, self.repeated_type_name())]) if self.type_def_condition(): type_def_list = self.single_var_type() if type_def_list: - ret_val.extend([(self.single_var_type(), self.type_name())]) + ret_val.extend([(type_def_list, self.type_name())]) return ret_val def type_def_bits(self): @@ -2268,7 +2287,7 @@ def single_func_prim(self, access, union_int=None, ptr_result=False): return (None, None) if self.type == "OTHER": - return self.my_types[self.value].single_func(access, union_int) + return self.my_types[self.value].single_func(access, union_int, ptr_result=ptr_result) func_name = self.single_func_prim_name(union_int, ptr_result=ptr_result) if func_name is None: @@ -2288,12 +2307,13 @@ def single_func_prim(self, access, union_int=None, ptr_result=False): return (func_name, arg) - def single_func(self, access=None, union_int=None): + def single_func(self, access=None, union_int=None, ptr_result=False): """Return the function name and arguments to call to encode/decode this element.""" if self.single_func_impl_condition(): return (self.xcode_func_name(), deref_if_not_null(access or self.var_access())) else: - return self.single_func_prim(access or self.val_access(), union_int) + return self.single_func_prim(access or self.val_access(), union_int, + ptr_result=ptr_result) def repeated_single_func(self, ptr_result=False): """Return the function name and arguments to call to encode/decode the repeated @@ -2308,6 +2328,7 @@ def has_backup(self): return (self.cbor_var_condition() or self.type in ["LIST", "MAP", "UNION"]) def num_backups(self): + """Calculate the number of state var backups needed for this element and all descendants.""" total = 0 if self.key: total += self.key.num_backups() @@ -2321,6 +2342,25 @@ def num_backups(self): total += 1 return total + def num_map_search_flags(self, in_map): + """Calculate the number of map search flags needed for this element and all descendants.""" + total_this = 0 # Number of flags needed for this element directly + total_descendants = 0 # Number of flags needed for descendants + if self.key: + total_descendants += sum(self.key.num_map_search_flags(False)) + total_this += 1 + if self.cbor_var_condition(): + total_descendants += sum(self.cbor.num_map_search_flags(False)) + if self.type in ["LIST", "MAP", "GROUP", "UNION"]: + child_in_map = in_map if self.type in ["GROUP", "UNION"] else self.type == "MAP" + total_descendants += \ + max([sum(child.num_map_search_flags(child_in_map)) for child in self.value]) + if self.type == "OTHER": + t, t_ex = self.my_types[self.value].num_map_search_flags(in_map) + total_this += t + total_descendants += t_ex + return total_this * self.max_qty, total_descendants + def depends_on(self): """Return a number indicating how many other elements this element depends on. @@ -2342,9 +2382,9 @@ def depends_on(self): return max(ret_vals) - def xcode_single_func_prim(self, union_int=None): + def xcode_single_func_prim(self, union_int=None, top_level=False): """Make a string from the list returned by single_func_prim()""" - return xcode_statement(*self.single_func_prim(self.val_access(), union_int)) + return xcode_statement(*self.single_func_prim(self.val_access(top_level), union_int)) def list_counts(self): """Recursively sum the total minimum and maximum element count for this element.""" @@ -2372,17 +2412,60 @@ def list_counts(self): }[self.type]()) return retval + def is_multiple_elem_group(self): + if (self.type == "GROUP") and (len(self.value) > 1) and (self.max_qty != self.min_qty): + return True + if self.type == "OTHER": + if self.my_types[self.value].list_counts() != (1, 1) and (self.max_qty != self.min_qty): + return True + return self.my_types[self.value].is_multiple_elem_group() + return False + + def is_valid_map_elem(self): + if self.unordered_maps: + if self.is_multiple_elem_group(): + return False, \ + "Groups with both variable repetitions and multiple elements are not allowed " \ + "in unordered maps." + return super().is_valid_map_elem() + + def elem_needs_map_smart_search(self, is_in_map): + if is_in_map \ + and self.unordered_maps \ + and ((self.max_qty > 1) or (self.key and not self.key.is_unambiguous_repeated())): + return True + + if self.type == "OTHER" \ + and (self.value not in self.entry_type_names or is_in_map) \ + and self.my_types[self.value].elem_needs_map_smart_search(is_in_map): + return True + + # Validation of child elements. + if self.type in ["MAP", "LIST", "UNION", "GROUP"]: + for child in self.value: + if child.elem_needs_map_smart_search( + self.type != "LIST" and (is_in_map or self.type == "MAP")): + return True + if self.cbor: + if self.cbor.elem_needs_map_smart_search(False): + return True + def xcode_list(self): """Return the full code needed to encode/decode a "LIST" or "MAP" element with children.""" start_func = f"zcbor_{self.type.lower()}_start_{self.mode}" end_func = f"zcbor_{self.type.lower()}_end_{self.mode}" end_func_force = f"zcbor_list_map_end_force_{self.mode}" + if self.type == "MAP" and self.mode == "decode" and self.unordered_maps: + start_func = "zcbor_unordered_map_start_decode" + end_func = "zcbor_unordered_map_end_decode" assert start_func in [ "zcbor_list_start_decode", "zcbor_list_start_encode", - "zcbor_map_start_decode", "zcbor_map_start_encode"] + "zcbor_map_start_decode", "zcbor_map_start_encode", + "zcbor_unordered_map_start_decode"] assert end_func in [ "zcbor_list_end_decode", "zcbor_list_end_encode", - "zcbor_map_end_decode", "zcbor_map_end_encode"] + "zcbor_map_end_decode", "zcbor_map_end_encode", + "zcbor_unordered_map_end_decode"] assert self.type in ["LIST", "MAP"], \ "Expected LIST or MAP type, was %s." % self.type _, max_counts = zip( @@ -2435,12 +2518,16 @@ def xcode_union(self): for i in range(1, len(child_values)): if ((not self.value[i].is_int_disambiguated()) and self.value[i - 1].simple_func_condition()): - child_values[i] = f"(zcbor_union_elem_code(state) && {child_values[i]})" - - return "(%s && (int_res = (%s), %s, int_res))" \ - % ("zcbor_union_start_code(state)", - f"{newl_ind}|| ".join(child_values), - "zcbor_union_end_code(state)") + if not self.value[i].key_var_condition() or not self.unordered_maps: + child_values[i] = f"(zcbor_union_elem_code(state) && {child_values[i]})" + + child_code = f"{newl_ind}|| ".join(child_values) + if len(self.value) > 0 \ + and (not self.value[0].key_var_condition() or not self.unordered_maps): + return f"(zcbor_union_start_code(state) "\ + + f"&& (int_res = ({child_code}), zcbor_union_end_code(state), int_res))" + else: + return f"({child_code})" else: return ternary_if_chain( self.choice_var_access(), @@ -2531,38 +2618,49 @@ def range_checks(self, access): return range_checks - def repeated_xcode(self, union_int=None): + def repeated_xcode(self, union_int=None, top_level=False): """Return the full code needed to encode/decode this element. Including children, key and cbor, excluding repetitions. """ val_union_int = union_int if not self.key else None # In maps, only pass union_int to key. - range_checks = self.range_checks(self.val_access()) + range_checks = self.range_checks(self.val_access(top_level)) + + def do_xcode_single_func_prim(inner_union_int=None): + return self.xcode_single_func_prim(union_int=inner_union_int, top_level=top_level) xcoder = { - "INT": self.xcode_single_func_prim, - "UINT": lambda: self.xcode_single_func_prim(val_union_int), - "NINT": lambda: self.xcode_single_func_prim(val_union_int), - "FLOAT": self.xcode_single_func_prim, + "INT": do_xcode_single_func_prim, + "UINT": lambda: do_xcode_single_func_prim(val_union_int), + "NINT": lambda: do_xcode_single_func_prim(val_union_int), + "FLOAT": do_xcode_single_func_prim, "BSTR": self.xcode_bstr, - "TSTR": self.xcode_single_func_prim, - "BOOL": self.xcode_single_func_prim, - "NIL": self.xcode_single_func_prim, - "UNDEF": self.xcode_single_func_prim, - "ANY": self.xcode_single_func_prim, + "TSTR": do_xcode_single_func_prim, + "BOOL": do_xcode_single_func_prim, + "NIL": do_xcode_single_func_prim, + "UNDEF": do_xcode_single_func_prim, + "ANY": do_xcode_single_func_prim, "LIST": self.xcode_list, "MAP": self.xcode_list, "GROUP": lambda: self.xcode_group(val_union_int), "UNION": self.xcode_union, - "OTHER": lambda: self.xcode_single_func_prim(val_union_int), + "OTHER": lambda: do_xcode_single_func_prim(val_union_int), }[self.type] xcoders = [] if self.key: - xcoders.append(self.key.full_xcode(union_int)) + if self.mode == "decode" and self.unordered_maps: + func, *arguments = self.key.single_func(self.key.val_access(), ptr_result=True) + x_args = xcode_args(*arguments) + xcoders.append( + f"zcbor_unordered_map_search((zcbor_{self.mode}r_t *){func}, {x_args})") + else: + xcoders.append(self.key.full_xcode(union_int=union_int)) if self.tags: xcoders.extend(self.xcode_tags()) if self.mode == "decode": xcoders.append(xcoder()) xcoders.extend(range_checks) + if self.key and self.unordered_maps: + xcoders.append("zcbor_elem_processed(state)") elif self.type == "BSTR" and self.cbor: xcoders.append(xcoder()) xcoders.extend(self.range_checks("tmp_str")) @@ -2579,7 +2677,7 @@ def result_len(self): else: return "sizeof(%s)" % self.repeated_type_name() - def full_xcode(self, union_int=None): + def full_xcode(self, union_int=None, top_level=False): """Return the full code needed to encode/decode this element. Including children, key, cbor, and repetitions. @@ -2612,11 +2710,11 @@ def full_xcode(self, union_int=None): xcode_args("*" + arg if arg != "NULL" and self.result_len() != "0" else arg), self.result_len())) else: - return self.repeated_xcode(union_int) + return self.repeated_xcode(union_int=union_int, top_level=top_level) def xcode(self): """Return the body of the encoder/decoder function for this element.""" - return self.full_xcode() + return self.full_xcode(top_level=True) def xcoders(self): """Recursively return a list of the bodies of the encoder/decoder functions for @@ -2637,7 +2735,9 @@ def xcoders(self): yield xcoder if self.repeated_single_func_impl_condition(): yield XcoderTuple( - self.repeated_xcode(), self.repeated_xcode_func_name(), self.repeated_type_name()) + self.repeated_xcode(top_level=True), + self.repeated_xcode_func_name(), + self.repeated_type_name()) if (self.single_func_impl_condition()): xcode_body = self.xcode() yield XcoderTuple(xcode_body, self.xcode_func_name(), self.type_name()) @@ -2661,6 +2761,8 @@ def __init__(self, entry_types, modes, print_time, default_max_qty, git_sha='', self.functions = dict() self.type_defs = dict() + self.needs_map_smart_search = dict() + # Sort type definitions so the typedefs will come in the correct order in the header file # and the function in the correct order in the c file. for mode in modes: @@ -2671,6 +2773,9 @@ def __init__(self, entry_types, modes, print_time, default_max_qty, git_sha='', self.functions[mode] = self.used_funcs(mode) self.type_defs[mode] = self.unique_types(mode) + self.needs_map_smart_search[mode] \ + = any(t.elem_needs_map_smart_search(False) for t in self.sorted_types[mode]) + self.version = __version__ if git_sha: @@ -2802,20 +2907,32 @@ def render_function(self, xcoder, mode): def render_entry_function(self, xcoder, mode): """Render a single entry function (API function) with signature and body.""" func_name, func_arg = (xcoder.xcode_func_name(), struct_ptr_name(mode)) + num_flag_states = "0" + num_flags = 0 + if xcoder.unordered_maps and mode == "decode": + num_flags = sum(xcoder.num_map_search_flags(False)) + num_flag_states = f"ZCBOR_FLAG_STATES({num_flags})" return f""" {xcoder.public_xcode_func_sig()} {{ - zcbor_state_t states[{xcoder.num_backups() + 2}]; + zcbor_state_t states[{xcoder.num_backups() + 2} + {num_flag_states}]; return zcbor_entry_function(payload, payload_len, (void *){func_arg}, payload_len_out, states, (zcbor_decoder_t *){func_name}, sizeof(states) / sizeof(zcbor_state_t), { - xcoder.list_counts()[1]}); + xcoder.list_counts()[1]}, {num_flags}); }}""" def render_file_header(self, line_prefix): lp = line_prefix return (f"\n{lp} " + self.file_header.replace("\n", f"\n{lp} ")).replace(" \n", "\n") + def render_smart_search_check(self): + return """ +#ifndef ZCBOR_MAP_SMART_SEARCH +#error "This file needs ZCBOR_MAP_SMART_SEARCH to function" +#endif +""" + def render_c_file(self, header_file_name, mode): """Render the entire generated C file contents.""" log_result_define = """#define log_result(state, result, func) \ @@ -2841,6 +2958,7 @@ def render_c_file(self, header_file_name, mode): #if DEFAULT_MAX_QTY != {self.default_max_qty} #error "The type file was generated with a different default_max_qty than this file" #endif +{self.render_smart_search_check() if self.needs_map_smart_search[mode] else ''} {log_result_define} @@ -2952,6 +3070,7 @@ def relativify(p): target_include_directories({target_name} PUBLIC {(linesep + " ").join(((str(relativify(f)) for f in include_dirs)))} ) +target_compile_definitions({target_name} PUBLIC ZCBOR_MAP_SMART_SEARCH) """ def render(self, modes, h_files, c_files, type_file, include_prefix, cmake_file=None, @@ -3111,6 +3230,14 @@ def parse_args(): help="""Header to be included in the comment at the top of generated files, e.g. copyright. Can be a string or a path to a file. If interpreted as a path to an existing file, the file's contents will be used.""") + code_parser.add_argument( + "--unordered-maps", required=False, action="store_true", default=False, + help="""Add support in the generated code for parsing maps with unknown element order. +When enabled, the generated code will use the zcbor_unordered_map_*() API to decode data +whenever inside a map. +zcbor detects when ZCBOR_MAP_SMART_SEARCH is needed and enable +This places restrictions on the level of ambiguity allowed between map keys in a map.""" + ) code_parser.set_defaults(process=process_code) validate_parent_parser = ArgumentParser(add_help=False) @@ -3213,7 +3340,7 @@ def process_code(args): for mode in modes: cddl_res[mode] = CodeGenerator.from_cddl( mode, cddl_contents, args.default_max_qty, mode, args.entry_types, - args.default_bit_size, short_names=args.short_names) + args.default_bit_size, unordered_maps=args.unordered_maps, short_names=args.short_names) # Parsing is done, pretty print the result. verbose_print(args.verbose, "Parsed CDDL types:")