Skip to content

Commit

Permalink
zcbor.py: Add basic support for .default in code generation.
Browse files Browse the repository at this point in the history
Support for default values in INT, TSTR, BSTR, FLOAT, BOOL

Signed-off-by: Øyvind Rønningstad <[email protected]>
  • Loading branch information
oyvindronningstad committed Nov 21, 2024
1 parent 186fd47 commit 00a0c1e
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 7 deletions.
4 changes: 4 additions & 0 deletions tests/cases/corner_cases.cddl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ ValueRange = [
lesseq1: uint .le 1,
equal42: uint .eq 42,
equalstrworld: tstr .eq "world",
default3: ?int .gt 2 .default 3,
defaulthello: ?bstr .default 'hello',
defaulte: ?float .size 4 .default 2.72,
defaultfalse: ?bool .default false,
]

SingleBstr = bstr
Expand Down
61 changes: 59 additions & 2 deletions tests/decode/test5_corner_cases/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,7 @@ ZTEST(cbor_decode_test5, test_range)
zassert_equal(ZCBOR_ERR_ITERATIONS, ret, "%d\r\n", ret);
}


ZTEST(cbor_decode_test5, test_value_range)
{
const uint8_t payload_value_range1[] = {LIST(6),
Expand All @@ -1118,13 +1119,17 @@ ZTEST(cbor_decode_test5, test_value_range)
END
};

const uint8_t payload_value_range2[] = {LIST(6),
const uint8_t payload_value_range2[] = {LIST(A),
0x18, 100, // 100
0x39, 0x03, 0xe8, // -1001
0x18, 100, // 100
0,
0x18, 42, // 42
0x65, 'w', 'o', 'r', 'l', 'd', // "world"
0x04,
0x42, 'h', 'i', // "hi"
0xFA, 0x40, 0x48, 0xf5, 0xc3,
0xF5,
END
};

Expand Down Expand Up @@ -1218,17 +1223,45 @@ ZTEST(cbor_decode_test5, test_value_range)
END
};

const uint8_t payload_value_range12_inv[] = {LIST(A),
0x18, 100, // 100
0x39, 0x03, 0xe8, // -1001
0x18, 100, // 100
0,
0x18, 42, // 42
0x65, 'w', 'o', 'r', 'l', 'd', // "world"
0x01, // TOO LOW
0x42, 'h', 'i', // "hi"
0xFA, 0x40, 0x48, 0xf5, 0xc3,
0xF5,
END
};

struct ValueRange exp_output_value_range1 = {
.greater10 = 11,
.less1000 = 999,
.greatereqmin10 = -10,
.lesseq1 = 1,
.default3 = 3,
.defaulthello = {
.value = "hello",
.len = 5,
},
.defaulte = 2.72,
.defaultfalse = false,
};
struct ValueRange exp_output_value_range2 = {
.greater10 = 100,
.less1000 = -1001,
.greatereqmin10 = 100,
.lesseq1 = 0,
.default3 = 4,
.defaulthello = {
.value = "hi",
.len = 2,
},
.defaulte = 3.14,
.defaultfalse = true,
};

struct ValueRange output;
Expand All @@ -1245,10 +1278,20 @@ ZTEST(cbor_decode_test5, test_value_range)
output.greatereqmin10, NULL);
zassert_equal(exp_output_value_range1.lesseq1,
output.lesseq1, NULL);
zassert_equal(exp_output_value_range1.default3,
output.default3, NULL);
zassert_equal(exp_output_value_range1.defaulthello.len,
output.defaulthello.len, NULL);
zassert_mem_equal(exp_output_value_range1.defaulthello.value,
output.defaulthello.value, output.defaulthello.len, NULL);
zassert_equal(exp_output_value_range1.defaulte,
output.defaulte, NULL);
zassert_equal(exp_output_value_range1.defaultfalse,
output.defaultfalse, NULL);

zassert_equal(ZCBOR_SUCCESS, cbor_decode_ValueRange(payload_value_range2, sizeof(payload_value_range2),
&output, &out_len), NULL);
zassert_equal(sizeof(payload_value_range2), out_len, NULL);
zassert_equal(sizeof(payload_value_range2), out_len, "%d != %d", sizeof(payload_value_range2), out_len);
zassert_equal(exp_output_value_range2.greater10,
output.greater10, NULL);
zassert_equal(exp_output_value_range2.less1000,
Expand All @@ -1257,6 +1300,16 @@ ZTEST(cbor_decode_test5, test_value_range)
output.greatereqmin10, NULL);
zassert_equal(exp_output_value_range2.lesseq1,
output.lesseq1, NULL);
zassert_equal(exp_output_value_range2.default3,
output.default3, NULL);
zassert_equal(exp_output_value_range2.defaulthello.len,
output.defaulthello.len, NULL);
zassert_mem_equal(exp_output_value_range2.defaulthello.value,
output.defaulthello.value, output.defaulthello.len, NULL);
zassert_equal(exp_output_value_range2.defaulte,
output.defaulte, NULL);
zassert_equal(exp_output_value_range2.defaultfalse,
output.defaultfalse, NULL);

zassert_equal(ZCBOR_ERR_WRONG_RANGE, cbor_decode_ValueRange(payload_value_range3_inv,
sizeof(payload_value_range3_inv), &output, &out_len), NULL);
Expand All @@ -1276,8 +1329,12 @@ ZTEST(cbor_decode_test5, test_value_range)
sizeof(payload_value_range10_inv), &output, &out_len), NULL);
zassert_equal(ZCBOR_ERR_WRONG_RANGE, cbor_decode_ValueRange(payload_value_range11_inv,
sizeof(payload_value_range11_inv), &output, &out_len), NULL);
// HIGH_ELEM_COUNT because the entry is optional, so decoding continues to the end of the list.
zassert_equal(ARR_ERR5, cbor_decode_ValueRange(payload_value_range12_inv,
sizeof(payload_value_range12_inv), &output, &out_len), NULL);
}


ZTEST(cbor_decode_test5, test_single)
{
uint8_t payload_single0[] = {0x45, 'h', 'e', 'l', 'l', 'o'};
Expand Down
2 changes: 2 additions & 0 deletions tests/include/common_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#define ARR_ERR2 ZCBOR_ERR_NO_PAYLOAD
#define ARR_ERR3 ZCBOR_ERR_WRONG_TYPE
#define ARR_ERR4 ZCBOR_ERR_PAYLOAD_NOT_CONSUMED
#define ARR_ERR5 ZCBOR_ERR_WRONG_TYPE
#define END 0xFF,
#define STR_LEN(len, lists) (len + lists)
#else
Expand All @@ -29,6 +30,7 @@
#define ARR_ERR2 ZCBOR_ERR_HIGH_ELEM_COUNT
#define ARR_ERR3 ZCBOR_ERR_NO_PAYLOAD
#define ARR_ERR4 ZCBOR_ERR_NO_PAYLOAD
#define ARR_ERR5 ZCBOR_ERR_HIGH_ELEM_COUNT
#define END
#define STR_LEN(len, lists) (len)
#endif
Expand Down
61 changes: 56 additions & 5 deletions zcbor/zcbor.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,20 @@ def ternary_if_chain(access, names, xcode_strings):
ternary_if_chain(access, names[1:], xcode_strings[1:]) if len(names) > 1 else "false")


def comma_operator(*expressions):
"""Add a C comma operator expression.
The individual expressions in arguments will be separated by commas.
"None" expressions are ignored."""
_expressions = list(e for e in expressions if e is not None)
if len(_expressions) == 0:
raise ValueError("comma operator must have at least one expression")
elif len(_expressions) == 1:
return _expressions[0]
else:
return f"({', '.join(_expressions)})"


val_conversions = {
(2**64) - 1: "UINT64_MAX",
(2**63) - 1: "INT64_MAX",
Expand Down Expand Up @@ -254,6 +268,8 @@ def __init__(self, default_max_qty, my_types, my_control_groups, base_name=None,
# "BOOL", "NIL", "UNDEF", "LIST", "MAP","GROUP", "UNION" and "OTHER". "OTHER" represents a
# CDDL type defined with '='.
self.type = None
# The default value of the element, as provided via the .default operator.
self.default = None
self.match_str = ""
self.errors = list()

Expand Down Expand Up @@ -561,6 +577,22 @@ def set_value(self, value_generator):
if self.type == "NINT":
self.max_value = -1

def set_default(self, value):
"""Set the default value of this element (provided via '.default')."""
if self.type not in ["INT", "UINT", "NINT", "BSTR", "TSTR", "FLOAT", "BOOL"]:
raise TypeError(f"zcbor does not support .default values for the {self.type} type")
if self.min_qty != 0 or self.max_qty != 1:
raise ValueError("zcbor currently supports .default only with the ? quantifier.")
if value.value is None:
raise ValueError(".default value must be unambiguous.")

if not self.type == value.type:
if not (self.type == "INT" and value.type in ["UINT", "NINT"]):
raise TypeError(f"Type of default does not match type of element. "
"({self.type} != {value.type})")

self.default = value.value

def type_and_range(self, new_type, min_val, max_val, inc_end=True):
"""Set the self.type and self.minValue and self.max_value (or self.min_size and
self.max_size depending on the type) of this element. For use during CDDL parsing.
Expand Down Expand Up @@ -888,6 +920,10 @@ def cddl_regexes_init(self):
lambda m_self, value: m_self.set_value(lambda: int(value, 0))),
(r'\.eq \"(?P<item>.*?)(?<!\\)\"',
lambda m_self, value: m_self.set_value(lambda: value)),
(r'\.default (\((?P<item>(?>[^\(\)]+|(?1))*)\))',
lambda m_self, type_str: m_self.set_default(m_self.parse(type_str)[0])),
(r'\.default (?P<item>[^\s,]+)',
lambda m_self, type_str: m_self.set_default(m_self.parse(type_str)[0])),
(r'\.cbor (\((?P<item>(?>[^\(\)]+|(?1))*)\))',
lambda m_self, type_str: m_self.set_cbor(m_self.parse(type_str)[0], False)),
(r'\.cbor (?P<item>[^\s,]+)',
Expand Down Expand Up @@ -1176,9 +1212,13 @@ def repeated_val_access(self):
return "NULL"
return self.access_append(self.var_name())

def optional_quantifier(self):
"""Whether the element has the "optional" quantifier ('?')."""
return (self.min_qty == 0 and isinstance(self.max_qty, int) and self.max_qty <= 1)

def present_var_condition(self):
"""Whether to include a "present" variable for this element."""
return self.min_qty == 0 and isinstance(self.max_qty, int) and self.max_qty <= 1
return self.optional_quantifier()

def count_var_condition(self):
"""Whether to include a "count" variable for this element."""
Expand Down Expand Up @@ -2605,13 +2645,24 @@ def full_xcode(self, union_int=None, top_level=False):
else:
assert self.mode == "decode", \
f"This code needs self.mode to be 'decode', not {self.mode}."
if not self.repeated_single_func_impl_condition():

assign = not self.repeated_single_func_impl_condition()
default_assignment = None
if self.default is not None:
default_value = (f"*({tmp_str_or_null(self.default)})"
if self.type in ["TSTR", "BSTR"] else val_to_str(self.default))
access = self.val_access() if assign else self.repeated_val_access()
default_assignment = f"({access} = {default_value})"
if assign:
decode_str = self.repeated_xcode(union_int)
return f"({self.present_var_access()} = {self.repeated_xcode(union_int)}, 1)"
return comma_operator(default_assignment,
f"{self.present_var_access()} = {decode_str}", "1")
func, *arguments = self.repeated_single_func(ptr_result=True)
return (
f"zcbor_present_decode(&(%s), (zcbor_decoder_t *)%s, %s)" %
return comma_operator(
default_assignment,
f"(zcbor_present_decode(&(%s), (zcbor_decoder_t *)%s, %s))" %
(self.present_var_access(), func, xcode_args(*arguments),))

elif self.count_var_condition():
func, arg = self.repeated_single_func(ptr_result=True)

Expand Down

0 comments on commit 00a0c1e

Please sign in to comment.