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 7, 2024
1 parent cf32d11 commit f79077e
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 4 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 .default 3,
defaulthello: ?bstr .default 'hello',
defaulte: ?float .size 4 .default 2.72,
defaultfalse: ?bool .default false,
]

SingleBstr = bstr
Expand Down
32 changes: 30 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, 0x42, 0xf6, 0xe9, 0x79,
0xF5,
END
};

Expand Down Expand Up @@ -1223,12 +1228,22 @@ ZTEST(cbor_decode_test5, test_value_range)
.less1000 = 999,
.greatereqmin10 = -10,
.lesseq1 = 1,
.default3 = 3,
.defaulthello = {
.value = "hello",
.len = 5,
},
};
struct ValueRange exp_output_value_range2 = {
.greater10 = 100,
.less1000 = -1001,
.greatereqmin10 = 100,
.lesseq1 = 0,
.default3 = 4,
.defaulthello = {
.value = "hi",
.len = 2,
},
};

struct ValueRange output;
Expand All @@ -1245,10 +1260,16 @@ 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(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 +1278,12 @@ 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(ZCBOR_ERR_WRONG_RANGE, cbor_decode_ValueRange(payload_value_range3_inv,
sizeof(payload_value_range3_inv), &output, &out_len), NULL);
Expand All @@ -1278,6 +1305,7 @@ ZTEST(cbor_decode_test5, test_value_range)
sizeof(payload_value_range11_inv), &output, &out_len), NULL);
}


ZTEST(cbor_decode_test5, test_single)
{
uint8_t payload_single0[] = {0x45, 'h', 'e', 'l', 'l', 'o'};
Expand Down
62 changes: 60 additions & 2 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: str | NoneType):
"""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 @@ -253,6 +267,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 @@ -559,6 +575,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 .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 @@ -885,6 +917,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 @@ -1166,9 +1202,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.default is None or self.mode == "encode") and self.optional_quantifier())

def count_var_condition(self):
"""Whether to include a "count" variable for this element."""
Expand Down Expand Up @@ -2593,11 +2633,29 @@ def full_xcode(self, union_int=None):
f"This code needs self.mode to be 'decode', not {self.mode}."
if not self.repeated_single_func_impl_condition():
decode_str = self.repeated_xcode(union_int)
return f"({self.present_var_access()} = {self.repeated_xcode(union_int)}, 1)"
return 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)" %
(self.present_var_access(), func, xcode_args(*arguments),))
elif self.mode == "decode" and self.optional_quantifier():
assert self.mode == "decode", \
f"This code needs self.mode to be 'decode', not {self.mode}."
assert self.default is not None, "Should only come here when there is a default value."
default_value = f"*({tmp_str_or_null(self.default)})" if self.type in ["TSTR", "BSTR"] \
else val_to_str(self.default)
default_assignment = f"({self.val_access()} = {default_value})"

if not self.repeated_single_func_impl_condition():
decode_str = self.repeated_xcode(union_int)
return comma_operator(default_assignment, decode_str, "1")
func, *arguments = self.repeated_single_func(ptr_result=True)
return comma_operator(
default_assignment,
f"(zcbor_present_decode(&(%s), (zcbor_decoder_t *)%s, %s))" %
(self.present_var_access(), func, xcode_args(*arguments),),
"1")

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

Expand Down

0 comments on commit f79077e

Please sign in to comment.