boot_serial: Replace cbor auto-generated code with zcbor functions

Replaces the auto-generated decoding/encoding files with inline code
for encoding/decoding cbor data structures, this adds the benefit of
allowing the elements to be in any order and reduces code size. To
accommodate this, zcbor_bulk has been imported from Zephyr.

Signed-off-by: Jamie McCrae <jamie.mccrae@nordicsemi.no>
diff --git a/boot/boot_serial/src/boot_serial.c b/boot/boot_serial/src/boot_serial.c
index c6c58bf..5f83ed2 100644
--- a/boot/boot_serial/src/boot_serial.c
+++ b/boot/boot_serial/src/boot_serial.c
@@ -36,16 +36,13 @@
 #include <zephyr/sys/crc.h>
 #include <zephyr/sys/base64.h>
 #include <hal/hal_flash.h>
-#include <zcbor_encode.h>
 #elif __ESPRESSIF__
-#include "zcbor_encode.h"
 #include <bootloader_utility.h>
 #include <esp_rom_sys.h>
 #include <esp_crc.h>
 #include <endian.h>
 #include <mbedtls/base64.h>
 #else
-#include "zcbor_encode.h"
 #include <bsp/bsp.h>
 #include <hal/hal_system.h>
 #include <hal/hal_flash.h>
@@ -55,6 +52,10 @@
 #include <base64/base64.h>
 #endif /* __ZEPHYR__ */
 
+#include <zcbor_decode.h>
+#include <zcbor_encode.h>
+#include "zcbor_bulk.h"
+
 #include <flash_map_backend/flash_map_backend.h>
 #include <os/os.h>
 #include <os/os_malloc.h>
@@ -74,12 +75,14 @@
 #include "single_loader.h"
 #endif
 
-#include "serial_recovery_cbor.h"
-#include "serial_recovery_echo.h"
 #include "bootutil/boot_hooks.h"
 
 BOOT_LOG_MODULE_DECLARE(mcuboot);
 
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE ZCBOR_ARRAY_SIZE
+#endif
+
 #ifndef MCUBOOT_SERIAL_MAX_RECEIVE_SIZE
 #define MCUBOOT_SERIAL_MAX_RECEIVE_SIZE 512
 #endif
@@ -383,10 +386,13 @@
     size_t img_chunk_off = SIZE_MAX;    /* Offset of image chunk within image  */
     uint8_t rem_bytes;                  /* Reminder bytes after aligning chunk write to
                                          * to flash alignment */
-    int img_num;
+    uint32_t img_num;
     size_t img_size_tmp = SIZE_MAX;     /* Temp variable for image size */
     const struct flash_area *fap = NULL;
     int rc;
+    struct zcbor_string img_chunk_data;
+    size_t decoded = 0;
+    bool ok;
 #ifdef MCUBOOT_ERASE_PROGRESSIVELY
     static off_t not_yet_erased = 0;    /* Offset of next byte to erase; writes to flash
                                          * are done in consecutive manner and erases are done
@@ -398,7 +404,25 @@
     static struct flash_sector status_sector;
 #endif
 
-    img_num = 0;
+    zcbor_state_t zsd[4];
+    zcbor_new_state(zsd, sizeof(zsd) / sizeof(zcbor_state_t), (uint8_t *)buf, len, 1);
+
+    struct zcbor_map_decode_key_val image_upload_decode[] = {
+        ZCBOR_MAP_DECODE_KEY_DECODER("image", zcbor_uint32_decode, &img_num),
+        ZCBOR_MAP_DECODE_KEY_DECODER("data", zcbor_bstr_decode, &img_chunk_data),
+        ZCBOR_MAP_DECODE_KEY_DECODER("len", zcbor_size_decode, &img_size_tmp),
+        ZCBOR_MAP_DECODE_KEY_DECODER("off", zcbor_size_decode, &img_chunk_off),
+    };
+
+    ok = zcbor_map_decode_bulk(zsd, image_upload_decode, ARRAY_SIZE(image_upload_decode),
+                               &decoded) == 0;
+
+    if (!ok) {
+        goto out_invalid_data;
+    }
+
+    img_chunk = img_chunk_data.value;
+    img_chunk_len = img_chunk_data.len;
 
     /*
      * Expected data format.
@@ -410,37 +434,6 @@
      * }
      */
 
-    struct Upload upload;
-    size_t decoded_len;
-    uint_fast8_t result = cbor_decode_Upload((const uint8_t *)buf, len, &upload, &decoded_len);
-
-    if ((result != ZCBOR_SUCCESS) || (len != decoded_len)) {
-        goto out_invalid_data;
-    }
-
-    for (int i = 0; i < upload._Upload_members_count; i++) {
-        struct Member_ *member = &upload._Upload_members[i]._Upload_members;
-        switch(member->_Member_choice) {
-            case _Member_image:
-                img_num = member->_Member_image;
-                break;
-            case _Member_data:
-                img_chunk = member->_Member_data.value;
-                img_chunk_len = member->_Member_data.len;
-                break;
-            case _Member_len:
-                img_size_tmp = member->_Member_len;
-                break;
-            case _Member_off:
-                img_chunk_off = member->_Member_off;
-                break;
-            case _Member_sha:
-            default:
-                /* Nothing to do. */
-                break;
-        }
-    }
-
     if (img_chunk_off == SIZE_MAX || img_chunk == NULL) {
         /*
          * Offset must be set in every block.
@@ -478,7 +471,6 @@
          }
 #endif
 
-
 #if defined(MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE)
         /* We are using swap state at end of flash area to store validation
          * result. Make sure the user cannot write it from an image to skip validation.
@@ -656,22 +648,38 @@
 static void
 bs_echo(char *buf, int len)
 {
-    struct Echo echo = { 0 };
-    size_t decoded_len;
+    struct zcbor_string value = { 0 };
+    struct zcbor_string key;
+    bool ok;
     uint32_t rc = MGMT_ERR_EINVAL;
-    uint_fast8_t result = cbor_decode_Echo((const uint8_t *)buf, len, &echo, &decoded_len);
 
-    if ((result != ZCBOR_SUCCESS) || (len != decoded_len)) {
+    zcbor_state_t zsd[4];
+    zcbor_new_state(zsd, sizeof(zsd) / sizeof(zcbor_state_t), (uint8_t *)buf, len, 1);
+
+    if (!zcbor_map_start_decode(zsd)) {
         goto out;
     }
 
-    if (echo._Echo_d.value == NULL) {
+    do {
+        ok = zcbor_tstr_decode(zsd, &key);
+
+        if (ok) {
+            if (key.len == 1 && *key.value == 'd') {
+                ok = zcbor_tstr_decode(zsd, &value);
+                break;
+            }
+
+            ok = zcbor_any_skip(zsd, NULL);
+        }
+    } while (ok);
+
+    if (!ok || !zcbor_map_end_decode(zsd)) {
         goto out;
     }
 
     zcbor_map_start_encode(cbor_state, 10);
     zcbor_tstr_put_term(cbor_state, "r");
-    if (zcbor_tstr_encode(cbor_state, &echo._Echo_d) && zcbor_map_end_encode(cbor_state, 10)) {
+    if (zcbor_tstr_encode(cbor_state, &value) && zcbor_map_end_encode(cbor_state, 10)) {
         boot_serial_output();
         return;
     } else {
diff --git a/boot/boot_serial/src/echo.cddl b/boot/boot_serial/src/echo.cddl
deleted file mode 100644
index 42ce0ba..0000000
--- a/boot/boot_serial/src/echo.cddl
+++ /dev/null
@@ -1,9 +0,0 @@
-;
-; Copyright (c) 2022 Nordic Semiconductor ASA
-;
-; SPDX-License-Identifier: Apache-2.0
-;
-
-Echo = {
-	"d" => tstr
-}
diff --git a/boot/boot_serial/src/regenerate_serial_recovery_cbor.sh b/boot/boot_serial/src/regenerate_serial_recovery_cbor.sh
index b56c270..33fa630 100755
--- a/boot/boot_serial/src/regenerate_serial_recovery_cbor.sh
+++ b/boot/boot_serial/src/regenerate_serial_recovery_cbor.sh
@@ -22,10 +22,6 @@
 	add_copy_notice $2 "copied"
 }
 
-
-echo "Generating serial_recovery_cbor.c|h"
-zcbor code -c serial_recovery.cddl -d -t Upload --oc serial_recovery_cbor.c --oh serial_recovery_cbor.h --time-header --copy-sources
-
 add_copyright() {
 echo "$(printf '/*
  * Copyright (c) %s
@@ -36,9 +32,6 @@
 ' "$2"; cat $1;)" > $1
 }
 
-add_copyright serial_recovery_cbor.c "$1"
-add_copyright serial_recovery_cbor.h "$1"
-add_copyright serial_recovery_cbor_types.h "$1"
 add_copy_notice zcbor_decode.c "copied"
 add_copy_notice zcbor_encode.c "copied"
 add_copy_notice zcbor_common.c "copied"
diff --git a/boot/boot_serial/src/serial_recovery.cddl b/boot/boot_serial/src/serial_recovery.cddl
deleted file mode 100644
index d45db08..0000000
--- a/boot/boot_serial/src/serial_recovery.cddl
+++ /dev/null
@@ -1,15 +0,0 @@
-;
-; Copyright (c) 2020 Nordic Semiconductor ASA
-;
-; SPDX-License-Identifier: Apache-2.0
-;
-
-Member = ("image" => int) /
-	("data" => bstr) /
-	("len" => int) /
-	("off" => int) /
-	("sha" => bstr)
-
-Upload = {
-	1**5members: Member
-}
diff --git a/boot/boot_serial/src/serial_recovery_cbor.c b/boot/boot_serial/src/serial_recovery_cbor.c
deleted file mode 100644
index b54fac4..0000000
--- a/boot/boot_serial/src/serial_recovery_cbor.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (c) 2023 Nordic Semiconductor ASA
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/*
- * Generated using zcbor version 0.7.0
- * https://github.com/NordicSemiconductor/zcbor
- * at: 2023-04-14 11:34:28
- * Generated with a --default-max-qty of 3
- */
-
-#include <stdint.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <string.h>
-#include <zcbor_decode.h>
-
-#include "serial_recovery_cbor.h"
-
-#if DEFAULT_MAX_QTY != 3
-#error "The type file was generated with a different default_max_qty than this file"
-#endif
-
-static bool decode_Member(zcbor_state_t *state, struct Member_ *result);
-static bool decode_repeated_Upload_members(zcbor_state_t *state, struct Upload_members *result);
-static bool decode_Upload(zcbor_state_t *state, struct Upload *result);
-
-
-static bool decode_Member(
-		zcbor_state_t *state, struct Member_ *result)
-{
-	zcbor_print("%s\r\n", __func__);
-	struct zcbor_string tmp_str;
-	bool int_res;
-
-	bool tmp_result = (((zcbor_union_start_code(state) && (int_res = (((((zcbor_tstr_expect(state, ((tmp_str.value = (uint8_t *)"image", tmp_str.len = sizeof("image") - 1, &tmp_str)))))
-	&& (zcbor_int32_decode(state, (&(*result)._Member_image)))) && (((*result)._Member_choice = _Member_image), true))
-	|| (zcbor_union_elem_code(state) && ((((zcbor_tstr_expect(state, ((tmp_str.value = (uint8_t *)"data", tmp_str.len = sizeof("data") - 1, &tmp_str)))))
-	&& (zcbor_bstr_decode(state, (&(*result)._Member_data)))) && (((*result)._Member_choice = _Member_data), true)))
-	|| (zcbor_union_elem_code(state) && ((((zcbor_tstr_expect(state, ((tmp_str.value = (uint8_t *)"len", tmp_str.len = sizeof("len") - 1, &tmp_str)))))
-	&& (zcbor_int32_decode(state, (&(*result)._Member_len)))) && (((*result)._Member_choice = _Member_len), true)))
-	|| (zcbor_union_elem_code(state) && ((((zcbor_tstr_expect(state, ((tmp_str.value = (uint8_t *)"off", tmp_str.len = sizeof("off") - 1, &tmp_str)))))
-	&& (zcbor_int32_decode(state, (&(*result)._Member_off)))) && (((*result)._Member_choice = _Member_off), true)))
-	|| (zcbor_union_elem_code(state) && ((((zcbor_tstr_expect(state, ((tmp_str.value = (uint8_t *)"sha", tmp_str.len = sizeof("sha") - 1, &tmp_str)))))
-	&& (zcbor_bstr_decode(state, (&(*result)._Member_sha)))) && (((*result)._Member_choice = _Member_sha), true)))), zcbor_union_end_code(state), int_res))));
-
-	if (!tmp_result)
-		zcbor_trace();
-
-	return tmp_result;
-}
-
-static bool decode_repeated_Upload_members(
-		zcbor_state_t *state, struct Upload_members *result)
-{
-	zcbor_print("%s\r\n", __func__);
-
-	bool tmp_result = (((decode_Member(state, (&(*result)._Upload_members)))));
-
-	if (!tmp_result)
-		zcbor_trace();
-
-	return tmp_result;
-}
-
-static bool decode_Upload(
-		zcbor_state_t *state, struct Upload *result)
-{
-	zcbor_print("%s\r\n", __func__);
-
-	bool tmp_result = (((zcbor_map_start_decode(state) && ((zcbor_multi_decode(1, 5, &(*result)._Upload_members_count, (zcbor_decoder_t *)decode_repeated_Upload_members, state, (&(*result)._Upload_members), sizeof(struct Upload_members))) || (zcbor_list_map_end_force_decode(state), false)) && zcbor_map_end_decode(state))));
-
-	if (!tmp_result)
-		zcbor_trace();
-
-	return tmp_result;
-}
-
-
-
-int cbor_decode_Upload(
-		const uint8_t *payload, size_t payload_len,
-		struct Upload *result,
-		size_t *payload_len_out)
-{
-	zcbor_state_t states[4];
-
-	zcbor_new_state(states, sizeof(states) / sizeof(zcbor_state_t), payload, payload_len, 1);
-
-	bool ret = decode_Upload(states, result);
-
-	if (ret && (payload_len_out != NULL)) {
-		*payload_len_out = MIN(payload_len,
-				(size_t)states[0].payload - (size_t)payload);
-	}
-
-	if (!ret) {
-		int err = zcbor_pop_error(states);
-
-		zcbor_print("Return error: %d\r\n", err);
-		return (err == ZCBOR_SUCCESS) ? ZCBOR_ERR_UNKNOWN : err;
-	}
-	return ZCBOR_SUCCESS;
-}
diff --git a/boot/boot_serial/src/serial_recovery_cbor.h b/boot/boot_serial/src/serial_recovery_cbor.h
deleted file mode 100644
index 6bec4e7..0000000
--- a/boot/boot_serial/src/serial_recovery_cbor.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (c) 2023 Nordic Semiconductor ASA
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/*
- * Generated using zcbor version 0.7.0
- * https://github.com/NordicSemiconductor/zcbor
- * at: 2023-04-14 11:34:28
- * Generated with a --default-max-qty of 3
- */
-
-#ifndef SERIAL_RECOVERY_CBOR_H__
-#define SERIAL_RECOVERY_CBOR_H__
-
-#include <stdint.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <string.h>
-#include <zcbor_decode.h>
-
-#include "serial_recovery_cbor_types.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#if DEFAULT_MAX_QTY != 3
-#error "The type file was generated with a different default_max_qty than this file"
-#endif
-
-
-int cbor_decode_Upload(
-		const uint8_t *payload, size_t payload_len,
-		struct Upload *result,
-		size_t *payload_len_out);
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* SERIAL_RECOVERY_CBOR_H__ */
diff --git a/boot/boot_serial/src/serial_recovery_cbor_types.h b/boot/boot_serial/src/serial_recovery_cbor_types.h
deleted file mode 100644
index 57b1095..0000000
--- a/boot/boot_serial/src/serial_recovery_cbor_types.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (c) 2023 Nordic Semiconductor ASA
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/*
- * Generated using zcbor version 0.7.0
- * https://github.com/NordicSemiconductor/zcbor
- * at: 2023-04-14 11:34:28
- * Generated with a --default-max-qty of 3
- */
-
-#ifndef SERIAL_RECOVERY_CBOR_TYPES_H__
-#define SERIAL_RECOVERY_CBOR_TYPES_H__
-
-#include <stdint.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <string.h>
-#include <zcbor_decode.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/** Which value for --default-max-qty this file was created with.
- *
- *  The define is used in the other generated file to do a build-time
- *  compatibility check.
- *
- *  See `zcbor --help` for more information about --default-max-qty
- */
-#define DEFAULT_MAX_QTY 3
-
-struct Member_ {
-	union {
-		struct {
-			int32_t _Member_image;
-		};
-		struct {
-			struct zcbor_string _Member_data;
-		};
-		struct {
-			int32_t _Member_len;
-		};
-		struct {
-			int32_t _Member_off;
-		};
-		struct {
-			struct zcbor_string _Member_sha;
-		};
-	};
-	enum {
-		_Member_image,
-		_Member_data,
-		_Member_len,
-		_Member_off,
-		_Member_sha,
-	} _Member_choice;
-};
-
-struct Upload_members {
-	struct Member_ _Upload_members;
-};
-
-struct Upload {
-	struct Upload_members _Upload_members[5];
-	size_t _Upload_members_count;
-};
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* SERIAL_RECOVERY_CBOR_TYPES_H__ */
diff --git a/boot/boot_serial/src/serial_recovery_echo.c b/boot/boot_serial/src/serial_recovery_echo.c
deleted file mode 100644
index 8b10a01..0000000
--- a/boot/boot_serial/src/serial_recovery_echo.c
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Generated using zcbor version 0.4.99
- * https://github.com/NordicSemiconductor/zcbor
- * Generated with a --default-max-qty of 3
- */
-
-#include <stdint.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <string.h>
-#include <zcbor_decode.h>
-
-#include "serial_recovery_echo.h"
-
-#if DEFAULT_MAX_QTY != 3
-#error "The type file was generated with a different default_max_qty than this file"
-#endif
-
-static bool decode_Echo(zcbor_state_t *state, struct Echo *result);
-
-static bool decode_Echo(
-		zcbor_state_t *state, struct Echo *result)
-{
-	zcbor_print("%s\r\n", __func__);
-	struct zcbor_string tmp_str;
-
-	bool tmp_result = (((zcbor_map_start_decode(state) && (((((zcbor_tstr_expect(state, ((tmp_str.value = (uint8_t *)"d", tmp_str.len = sizeof("d") - 1, &tmp_str)))))
-	&& (zcbor_tstr_decode(state, (&(*result)._Echo_d))))) || (zcbor_list_map_end_force_decode(state), false)) && zcbor_map_end_decode(state))));
-
-	if (!tmp_result)
-		zcbor_trace();
-
-	return tmp_result;
-}
-
-
-
-int cbor_decode_Echo(
-		const uint8_t *payload, size_t payload_len,
-		struct Echo *result,
-		size_t *payload_len_out)
-{
-	zcbor_state_t states[3];
-
-	zcbor_new_state(states, sizeof(states) / sizeof(zcbor_state_t), payload, payload_len, 1);
-
-	bool ret = decode_Echo(states, result);
-
-	if (ret && (payload_len_out != NULL)) {
-		*payload_len_out = MIN(payload_len,
-				(size_t)states[0].payload - (size_t)payload);
-	}
-
-	if (!ret) {
-		int err = zcbor_pop_error(states);
-
-		zcbor_print("Return error: %d\r\n", err);
-		return (err == ZCBOR_SUCCESS) ? ZCBOR_ERR_UNKNOWN : err;
-	}
-	return ZCBOR_SUCCESS;
-}
diff --git a/boot/boot_serial/src/serial_recovery_echo.h b/boot/boot_serial/src/serial_recovery_echo.h
deleted file mode 100644
index 9a5d691..0000000
--- a/boot/boot_serial/src/serial_recovery_echo.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Generated using zcbor version 0.4.99
- * https://github.com/NordicSemiconductor/zcbor
- * Generated with a --default-max-qty of 3
- */
-
-#ifndef SERIAL_RECOVERY_ECHO_H__
-#define SERIAL_RECOVERY_ECHO_H__
-
-#include <stdint.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <string.h>
-#include <zcbor_decode.h>
-
-#include "serial_recovery_echo_types.h"
-
-#if DEFAULT_MAX_QTY != 3
-#error "The type file was generated with a different default_max_qty than this file"
-#endif
-
-
-int cbor_decode_Echo(
-		const uint8_t *payload, size_t payload_len,
-		struct Echo *result,
-		size_t *payload_len_out);
-
-
-#endif /* SERIAL_RECOVERY_ECHO_H__ */
diff --git a/boot/boot_serial/src/serial_recovery_echo_types.h b/boot/boot_serial/src/serial_recovery_echo_types.h
deleted file mode 100644
index 0473fea..0000000
--- a/boot/boot_serial/src/serial_recovery_echo_types.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Generated using zcbor version 0.4.99
- * https://github.com/NordicSemiconductor/zcbor
- * Generated with a --default-max-qty of 3
- */
-
-#ifndef SERIAL_RECOVERY_ECHO_TYPES_H__
-#define SERIAL_RECOVERY_ECHO_TYPES_H__
-
-#include <stdint.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <string.h>
-#include <zcbor_decode.h>
-
-/** Which value for --default-max-qty this file was created with.
- *
- *  The define is used in the other generated file to do a build-time
- *  compatibility check.
- *
- *  See `zcbor --help` for more information about --default-max-qty
- */
-#define DEFAULT_MAX_QTY 3
-
-struct Echo {
-	struct zcbor_string _Echo_d;
-};
-
-
-#endif /* SERIAL_RECOVERY_ECHO_TYPES_H__ */
diff --git a/boot/boot_serial/src/zcbor_bulk.c b/boot/boot_serial/src/zcbor_bulk.c
new file mode 100644
index 0000000..35ae0bd
--- /dev/null
+++ b/boot/boot_serial/src/zcbor_bulk.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include "zcbor_bulk.h"
+
+int zcbor_map_decode_bulk(zcbor_state_t *zsd, struct zcbor_map_decode_key_val *map,
+                          size_t map_size, size_t *matched)
+{
+    bool ok;
+    struct zcbor_map_decode_key_val *dptr = map;
+
+    if (!zcbor_map_start_decode(zsd)) {
+        return -EBADMSG;
+    }
+
+    *matched = 0;
+    ok = true;
+
+    do {
+        struct zcbor_string key;
+        bool found = false;
+        size_t map_count = 0;
+
+        ok = zcbor_tstr_decode(zsd, &key);
+
+        while (ok && map_count < map_size) {
+            if (dptr >= (map + map_size)) {
+                dptr = map;
+            }
+
+            if (key.len == dptr->key.len        &&
+                memcmp(key.value, dptr->key.value, key.len) == 0) {
+
+                if (dptr->found) {
+                    return -EADDRINUSE;
+                }
+                if (!dptr->decoder(zsd, dptr->value_ptr)) {
+                    /* Failure to decode value matched to key
+                     * means that either decoder has been
+                     * incorrectly assigned or SMP payload
+                     * is broken anyway.
+                     */
+                    return -ENOMSG;
+                }
+
+                dptr->found = true;
+                found = true;
+                ++dptr;
+                ++(*matched);
+                break;
+            }
+
+            ++dptr;
+            ++map_count;
+        }
+
+        if (!found && ok) {
+            ok = zcbor_any_skip(zsd, NULL);
+        }
+    } while (ok);
+
+    return zcbor_map_end_decode(zsd) ? 0 : -EBADMSG;
+}
diff --git a/boot/boot_serial/src/zcbor_bulk.h b/boot/boot_serial/src/zcbor_bulk.h
new file mode 100644
index 0000000..3b7c42f
--- /dev/null
+++ b/boot/boot_serial/src/zcbor_bulk.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2022 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef H_ZCBOR_BULK_PRIV_
+#define H_ZCBOR_BULK_PRIV_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __ZEPHYR__
+#include <zcbor_common.h>
+#include <zcbor_decode.h>
+#else
+#include "zcbor_common.h"
+#include "zcbor_decode.h"
+#endif
+
+/** @cond INTERNAL_HIDDEN */
+
+struct zcbor_map_decode_key_val {
+    struct zcbor_string key;     /* Map key string */
+    zcbor_decoder_t *decoder;    /* Key corresponding decoder */
+    void *value_ptr;
+    bool found;
+};
+
+/** @brief Define single key-decoder mapping
+ *
+ * The macro creates a single zcbor_map_decode_key_val type object.
+ *
+ * @param k     key is "" enclosed string representing key;
+ * @param dec   decoder function; this should be zcbor_decoder_t
+ *              type function from zcbor or a user provided implementation
+ *              compatible with the type.
+ * @param vp    non-NULL pointer for result of decoding; should correspond
+ *              to type served by decoder function for the mapping.
+ */
+#define ZCBOR_MAP_DECODE_KEY_DECODER(k, dec, vp) \
+    {                                            \
+        {                                        \
+            .value = (uint8_t *)k,               \
+            .len = sizeof(k) - 1,                \
+        },                                       \
+        .decoder = (zcbor_decoder_t *)dec,       \
+        .value_ptr = vp,                         \
+    }
+
+/** @brief Define single key-value decode mapping
+ *
+ * ZCBOR_MAP_DECODE_KEY_DECODER should be used instead of this macro as,
+ * this macro does not allow keys with whitespaces embeeded, which CBOR
+ * does allow.
+ *
+ * The macro creates a single zcbor_map_decode_key_val type object.
+ *
+ * @param k    key; the @p k will be stringified so should be given
+ *             without "";
+ * @param dec  decoder function; this should be zcbor_decoder_t
+ *             type function from zcbor or a user provided implementation
+ *             compatible with the type.
+ * @param vp   non-NULL pointer for result of decoding; should correspond
+ *             to type served by decoder function for the mapping.
+ */
+#define ZCBOR_MAP_DECODE_KEY_VAL(k, dec, vp)            \
+    ZCBOR_MAP_DECODE_KEY_DECODER(STRINGIFY(k), dec, vp)
+
+/** @brief Decodes single level map according to a provided key-decode map.
+ *
+ * The function takes @p map of key to decoder array defined as:
+ *
+ *    struct zcbor_map_decode_key_val map[] = {
+ *        ZCBOR_MAP_DECODE_KEY_DECODER("key0", decode_fun0, val_ptr0),
+ *        ZCBOR_MAP_DECODE_KEY_DECODER("key1", decode_fun1, val_ptr1),
+ *        ...
+ *    };
+ *
+ * where "key?" is string representing key; the decode_fun? is
+ * zcbor_decoder_t compatible function, either from zcbor or defined by
+ * user; val_ptr? are pointers to variables where decoder function for
+ * a given key will place a decoded value - they have to agree in type
+ * with decoder function.
+ *
+ * Failure to decode any of values will cause the function to return
+ * negative error, and leave the map open: map is broken anyway or key-decoder
+ * mapping is broken, and we can not really decode the map.
+ *
+ * Note that the function opens map by itself and will fail if map
+ * is already opened.
+ *
+ * @param zsd        zcbor decoder state;
+ * @param map        key-decoder mapping list;
+ * @param map_size   size of maps, both maps have to have the same size;
+ * @param matched    pointer to the  counter of matched keys, zeroed upon
+ *                   successful map entry and incremented only for successful
+ *                   decoded fields.
+ * @return           0 when the whole map has been parsed, there have been
+ *                   no decoding errors, and map has been closed successfully;
+ *                   -ENOMSG when given decoder function failed to decode
+ *                   value;
+ *                   -EADDRINUSE when key appears twice within map, map is then
+ *                   parsed up to they key that has appeared twice;
+ *                   -EBADMSG when failed to close map.
+ */
+int zcbor_map_decode_bulk(zcbor_state_t *zsd, struct zcbor_map_decode_key_val *map,
+                          size_t map_size, size_t *matched);
+
+/** @endcond */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_ZCBOR_BULK_PRIV_ */
diff --git a/boot/espressif/CMakeLists.txt b/boot/espressif/CMakeLists.txt
index 90dd749..2172869 100644
--- a/boot/espressif/CMakeLists.txt
+++ b/boot/espressif/CMakeLists.txt
@@ -221,7 +221,7 @@
 
     list(APPEND bootutil_srcs
         ${BOOT_SERIAL_DIR}/src/boot_serial.c
-        ${BOOT_SERIAL_DIR}/src/serial_recovery_cbor.c
+        ${BOOT_SERIAL_DIR}/src/zcbor_bulk.c
         ${ZCBOR_DIR}/src/zcbor_decode.c
         ${ZCBOR_DIR}/src/zcbor_encode.c
         ${ZCBOR_DIR}/src/zcbor_common.c
diff --git a/boot/zephyr/CMakeLists.txt b/boot/zephyr/CMakeLists.txt
index 998464b..d9d474e 100644
--- a/boot/zephyr/CMakeLists.txt
+++ b/boot/zephyr/CMakeLists.txt
@@ -238,9 +238,7 @@
 if(CONFIG_MCUBOOT_SERIAL)
   zephyr_sources(${BOOT_DIR}/zephyr/serial_adapter.c)
   zephyr_sources(${BOOT_DIR}/boot_serial/src/boot_serial.c)
-  zephyr_sources(${BOOT_DIR}/boot_serial/src/serial_recovery_cbor.c)
-
-  zephyr_sources_ifdef(CONFIG_BOOT_MGMT_ECHO ${BOOT_DIR}/boot_serial/src/serial_recovery_echo.c)
+  zephyr_sources(${BOOT_DIR}/boot_serial/src/zcbor_bulk.c)
 
   zephyr_include_directories(${BOOT_DIR}/bootutil/include)
   zephyr_include_directories(${BOOT_DIR}/boot_serial/include)