While working on the new standard data type set for v1.0 I noticed that getting byte alignment right requires a bit of attention and it is easy to mess things up by miscalculating the bit length of array length prefixes and union tags. It would help a lot if one could drop a directive right in the definition that would instruct the compiler to check a simple condition and abort compilation if the condition is not met.
Simple arithmetic checks against the bit offset from the beginning of the message allow one to ensure that a given field is bit-aligned and that the size of the message does not exceed a certain limit. The latter is especially useful for standard messages as we want to ensure that no message can be larger than 400 bytes, to maximize compatibility with resource-constrained nodes.
For example:
uint7 node_id
void1
# Suppose that we want to ensure that the following field is byte-aligned:
@assert offset % 8 == 0
uint8[16] unique_id
# Now checking that we got the size of the message right
@assert offset / 8 == 17
The above approach is simple but breaks on variable-size structures. For those, we can use ranges instead.
uint7 node_id
void1
# Suppose that we want to ensure that the following field is byte-aligned:
@assert offset % 8 == 0
void3
uint8[<=16] unique_id
# Can't use offset anymore because after the above field it cannot be
# determined statically. Using ranges instead.
@assert min_offset / 8 == 2
@assert max_offset / 8 == 18
# Making sure the message is not longer than a predefined limit
@assert max_offset / 8 < 400
Supporting both strict offset and its ranges (between min and max) is useful because if one were to look at the way most definitions are created they would see that variable-length fields tend to gravitate towards the end of the definition, so it makes sense to let the user rely on the strict offset estimate as much as possible for extra determinism. Additionally, having the strict offset provided directly in the definition helps users who can’t rely on code generation tools in their applications (e.g., up until recently Libcanard did not support message code generation, so one had to serialize/deserialize messages by fiddling with bit offsets manually, which is error-prone).
To simplify implementation of third-party compilers, the specification may make this directive optional to support, allowing compilers to ignore it. Probably the most tedious part of this feature would be to come up with a sufficiently rigorous definition of the syntax of arithmetic expressions.