Skip to content

Commit

Permalink
zcbor_decode.c: Add a function for printing CBOR data
Browse files Browse the repository at this point in the history
zcbor_print_cbor() prints the CBOR data pointed to by the state
variable, marked up with color and indentation.

Repurpose zcbor_any_skip() for the traversal.
Add helper functions to zcbor_print.h.

Add test for printing function.

Add info about the function to the README.

Signed-off-by: Øyvind Rønningstad <oyvind.ronningstad@nordicsemi.no>
  • Loading branch information
oyvindronningstad committed Dec 11, 2023
1 parent 528dd3e commit 3713890
Show file tree
Hide file tree
Showing 19 changed files with 1,039 additions and 27 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
platform: ["native_posix", "native_posix_64", "mps2_an521"]
asserts: ["", "-x VERBOSE=ON -x ASSERTS=ON"]
asserts: ["", "-x VERBOSE=ON -x ASSERTS=ON -e no_verbose"]
name: Merge tests 1 (${{ matrix.platform }}${{ matrix.asserts != '' && ' with asserts' || '' }})
steps:
- name: Checkout the code
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ ZCBOR_STATE_D(decode_state, n, payload, payload_len, elem_count, n_flags);
ZCBOR_STATE_E(encode_state, n, payload, payload_len, 0);
```
Diagnostic printing of CBOR data
--------------------------------
The C library contains a function (`zcbor_print_cbor()`) for printing CBOR data to terminal in a human readable way.
The functionality must be enabled via the `ZCBOR_PRINT_CBOR` configuration option for the function to be available.
By also defining `ZCBOR_PRINT_CBOR_PRETTY`, the output is made more readable via more thorough interpretation, and colorized using ANSI escape codes, see the figure below for the difference.
The color choices can be configured via the `ZCBOR_PRINT_CBOR_COLOR_*` (`HEADER`, `VALUE`, `DESC`, `TAG`), the defaults are red, blue, green, and yellow respectively.
If the `ZCBOR_PRINT_CBOR_PRETTY` functionality is wanted without the color, set all the `ZCBOR_*_COLOR` definitions to empty strings.
If changing the color, the defines should be full ANSI escape codes:
```c
/* Make the header green */
#define ZCBOR_PRINT_CBOR_COLOR_HEADER "\x1B[32m"
```

![`zcbor_print_cbor() output`](images/zcbor_print_cbor_ex1.png)
Figure: Example output from `zcbor_print_cbor()`, without and with `ZCBOR_PRINT_CBOR_PRETTY`.

By default, `zcbor_print_cbor()` uses Zephyr's `printk` to print.
To change this, define `ZCBOR_PRINT_FUNC` to your preferred function (must follow the `printf` signature).

Configuration
-------------

Expand All @@ -98,6 +119,9 @@ Name | Description
`ZCBOR_STOP_ON_ERROR` | Enable the `stop_on_error` functionality. This makes all functions abort their execution if called when an error has already happened.
`ZCBOR_BIG_ENDIAN` | All decoded values are returned as big-endian. The default is little-endian.
`ZCBOR_MAP_SMART_SEARCH` | Applies to decoding of unordered maps. When enabled, a flag is kept for each element in an array, ensuring it is not processed twice. If disabled, a count is kept for map as a whole. Enabling increases code size and memory usage, and requires the state variable to possess the memory necessary for the flags.
`ZCBOR_PRINT_CBOR` | Enable the `zcbor_print_cbor()` function for printing CBOR.
`ZCBOR_PRINT_CBOR_PRETTY` | Enable color printing and better readability for `zcbor_print_cbor()`. `ZCBOR_PRINT_CBOR` must also be defined. This leads to bigger code size.
`ZCBOR_PRINT_FUNC` | Function used for all printing (`zcbor_log*()`, `zcbor_trace()`, `zcbor_print*()`). When undefined, the default is `printf`


Python script and module
Expand Down
Binary file added images/zcbor_print_cbor_ex1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions include/zcbor_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ struct zcbor_string_fragment {
#define MAX(a, b) (((a) < (b)) ? (b) : (a))
#endif

#ifndef NAN
#define NAN (0.0/0.0)
#endif

#ifndef ZCBOR_ARRAY_SIZE
#define ZCBOR_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#endif
Expand Down
11 changes: 11 additions & 0 deletions include/zcbor_decode.h
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,17 @@ void zcbor_bstr_next_fragment(zcbor_state_t *state,
/** Can be used on any fragment to tell if it is the final fragment of the string. */
bool zcbor_is_last_fragment(const struct zcbor_string_fragment *fragment);

/** Diagnostic printing of CBOR data
*
* Traverses and prints one CBOR element, analogous to @ref zcbor_any_skip.
* Must be enabled via ZCBOR_PRINT_CBOR. Can be modified via
* ZCBOR_PRINT_CBOR_PRETTY, ZCBOR_PRINT_CBOR_COLOR_HEADER, ZCBOR_PRINT_CBOR_COLOR_VALUE,
* ZCBOR_PRINT_CBOR_COLOR_DESC, ZCBOR_PRINT_CBOR_COLOR_TAG, and ZCBOR_PRINT_FUNC.
*
* See the README for more information.
*/
bool zcbor_print_cbor(zcbor_state_t *state);

#ifdef __cplusplus
}
#endif
Expand Down
241 changes: 241 additions & 0 deletions include/zcbor_print.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,247 @@ static void zcbor_print_error(int error)
zcbor_do_print("%s\r\n", zcbor_error_str(error));
}

#ifdef ZCBOR_PRINT_CBOR
static bool indent_printed = false;

__attribute__((used))
static void zcbor_print_indent(size_t indent_len)
{
if (!indent_printed) {
for (int i = 0; i < indent_len; i++) {
zcbor_do_print("| ");
}
indent_printed = true;
}
}

__attribute__((used))
static void zcbor_print_newline(void)
{
zcbor_do_print("\r\n");
indent_printed = false;
}

#define BYTES_PER_LINE 16

__attribute__((used))
static void zcbor_print_str(const uint8_t *str, size_t len, size_t indent_len)
{
for (size_t i = 0; i < len; i++) {
if (!(i % BYTES_PER_LINE)) {
if (i > 0) {zcbor_print_newline();}
zcbor_print_indent(indent_len);
zcbor_do_print("0x");
}
zcbor_do_print("%02x ", str[i]);
}
}

__attribute__((used))
static void zcbor_print_bstr(uint8_t const *payload, size_t len, size_t indent_len)
{
zcbor_print_str(payload, len, indent_len);
zcbor_print_newline();
}

static void zcbor_print_tstr(uint8_t const *payload, size_t len, size_t indent_len);

__attribute__((used))
static void zcbor_print_btstr(uint8_t const *payload, size_t len, bool is_bstr, size_t indent_len)
{
if (is_bstr) {
zcbor_print_bstr(payload, len, indent_len);
} else {
zcbor_print_tstr(payload, len, indent_len);
}
}


#ifdef ZCBOR_PRINT_CBOR_PRETTY
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "zcbor_common.h"
#define RESET_COLOR "\x1B[0m"

#ifndef ZCBOR_PRINT_CBOR_COLOR_HEADER
#define ZCBOR_PRINT_CBOR_COLOR_HEADER "\x1B[31m" /* red */
#endif
#ifndef ZCBOR_PRINT_CBOR_COLOR_VALUE
#define ZCBOR_PRINT_CBOR_COLOR_VALUE "\x1B[34m" /* blue */
#endif
#ifndef ZCBOR_PRINT_CBOR_COLOR_DESC
#define ZCBOR_PRINT_CBOR_COLOR_DESC "\x1B[32m" /* green */
#endif
#ifndef ZCBOR_PRINT_CBOR_COLOR_TAG
#define ZCBOR_PRINT_CBOR_COLOR_TAG "\x1B[33m" /* yellow */
#endif


const static char *zcbor_header_byte_strings[] = {
"", "", "bstr", "tstr", "list", "map", "tag", "simple"
};


__attribute__((used))
static const char *zcbor_header_byte_str(uint8_t header_byte)
{
zcbor_major_type_t major_type = ZCBOR_MAJOR_TYPE(header_byte);
uint8_t additional = ZCBOR_ADDITIONAL(header_byte);
const char *simple_strings[] = {"false", "true", "nil", "undefined"};

if ((major_type < ZCBOR_MAJOR_TYPE_SIMPLE)
|| (additional < ZCBOR_BOOL_TO_SIMPLE)
|| (additional == ZCBOR_VALUE_IS_1_BYTE)) {
return zcbor_header_byte_strings[major_type];
} else if (additional <= ZCBOR_VALUE_IN_HEADER) {
return simple_strings[additional - ZCBOR_BOOL_TO_SIMPLE];
}

return "";
}

__attribute__((used))
static bool zcbor_print_float(int8_t additional, uint64_t value)
{
uint32_t value32;
double dvalue;

switch(additional) {
case ZCBOR_VALUE_IS_2_BYTES:
dvalue = (double)zcbor_float16_to_32((uint16_t)value);
break;
case ZCBOR_VALUE_IS_4_BYTES:
value32 = (uint32_t)value;
dvalue = (double)(*(float *)&value32);
break;
case ZCBOR_VALUE_IS_8_BYTES:
dvalue = *(double *)&value;
break;
default:
return false;
}
zcbor_do_print("%f", dvalue);
return true;
}

__attribute__((used))
static void zcbor_print_numeric(uint8_t header_byte, uint64_t value)
{
zcbor_major_type_t major_type = ZCBOR_MAJOR_TYPE(header_byte);
uint8_t additional = ZCBOR_ADDITIONAL(header_byte);

if (major_type == ZCBOR_MAJOR_TYPE_NINT) {
zcbor_do_print("-%llu", value + 1);
} else if (major_type == ZCBOR_MAJOR_TYPE_SIMPLE
&& additional >= ZCBOR_BOOL_TO_SIMPLE
&& additional <= ZCBOR_VALUE_IN_HEADER) {
/* Do nothing */
} else if (additional == ZCBOR_VALUE_IS_INDEFINITE_LENGTH) {
/* Do nothing */
} else if (major_type == ZCBOR_MAJOR_TYPE_SIMPLE
&& zcbor_print_float(additional, value)) {
/* Do nothing, already printed */
} else {
zcbor_do_print("%llu", value);
}
}

__attribute__((used))
static void zcbor_print_value(uint8_t const *payload, size_t len, uint64_t value, size_t indent_len)
{
uint8_t header_byte = *payload;

zcbor_print_indent(indent_len);
zcbor_do_print(ZCBOR_PRINT_CBOR_COLOR_HEADER "0x%02x " ZCBOR_PRINT_CBOR_COLOR_VALUE,
header_byte);
if (len > 0) {
zcbor_print_str(payload + 1, len - 1, 0);
}
zcbor_do_print(ZCBOR_PRINT_CBOR_COLOR_DESC "(%s", zcbor_header_byte_str(header_byte));
zcbor_print_numeric(header_byte, value);
zcbor_do_print(")" RESET_COLOR);
zcbor_print_newline();
}

__attribute__((used))
static void zcbor_print_tstr(uint8_t const *payload, size_t len, size_t indent_len)
{
zcbor_print_indent(indent_len);
zcbor_do_print("\"");
size_t prev_i = 0;

for (size_t i = 0; i < len; i++) {
if (payload[i] == '\n') {
/* Add indent after newlines. */
zcbor_do_print("%.*s", i - prev_i, payload + prev_i);
prev_i = i + 1;
zcbor_print_newline();
zcbor_print_indent(indent_len);
}
}
zcbor_do_print("%.*s\"", len - prev_i, payload + prev_i);
zcbor_print_newline();
}

__attribute__((used))
static void zcbor_print_tag(uint32_t tag, size_t indent_len)
{
zcbor_print_indent(indent_len);
zcbor_do_print(ZCBOR_PRINT_CBOR_COLOR_TAG "0x%02x ", tag);
}

__attribute__((used))
static void zcbor_print_end(zcbor_major_type_t major_type, size_t indent_len)
{
zcbor_print_indent(indent_len);
zcbor_do_print(ZCBOR_PRINT_CBOR_COLOR_HEADER "0xff " ZCBOR_PRINT_CBOR_COLOR_DESC "(%s end)" RESET_COLOR,
zcbor_header_byte_strings[major_type]);
zcbor_print_newline();
}

#else

__attribute__((used))
static void zcbor_print_tstr(uint8_t const *payload, size_t len, size_t indent_len)
{
zcbor_do_print("\"%.*s\"", len, payload);
zcbor_print_newline();
}

__attribute__((used))
static void zcbor_print_value(uint8_t const *payload, size_t len, uint64_t value, size_t indent_len)
{
zcbor_print_str(payload, len, indent_len);
if (len) {
if (ZCBOR_ADDITIONAL(*payload) == ZCBOR_VALUE_IS_INDEFINITE_LENGTH) {
zcbor_do_print("(start)");
} else {
zcbor_do_print("(%llu)", value);
}
zcbor_print_newline();
}
}

__attribute__((used))
static void zcbor_print_tag(uint32_t tag, size_t indent_len)
{
zcbor_print_indent(indent_len);
zcbor_do_print("0x%02x ", tag);
}

__attribute__((used))
static void zcbor_print_end(zcbor_major_type_t major_type, size_t indent_len)
{
zcbor_print_indent(indent_len);
zcbor_do_print("0xff (end)");
zcbor_print_newline();
}

#endif
#endif

#ifdef __cplusplus
}
#endif
Expand Down
1 change: 1 addition & 0 deletions samples/pet/src/pet_decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <stddef.h>
#include <string.h>
#include "zcbor_decode.h"
#include "zcbor_print.h"
#include "pet_decode.h"
#include "zcbor_print.h"

Expand Down
1 change: 1 addition & 0 deletions samples/pet/src/pet_encode.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <stddef.h>
#include <string.h>
#include "zcbor_encode.h"
#include "zcbor_print.h"
#include "pet_encode.h"
#include "zcbor_print.h"

Expand Down
Loading

0 comments on commit 3713890

Please sign in to comment.