Type safety enhancement proposal

Hi forum,

Following a long and sometimes heated discussing on a parallel thread, I’d like to introduce an additional proposal aimed at improving type safety and easy of use of UAVCANv1 based systems.
The idea is add an extra string-typed register for each publish and subscribe topic. This register would be read-only and contain a “type descriptor string” for the message that is either sent or received by that topic.
An example descriptor string would be "v48b1a2[{u8u8}]{u8u8}{u8u8}u64A16[u8]a50[u8]a1[u64]a222[u8]" (note this implementation is proof of concept and therefore optimized only for human readability and not for size).
This signature string can be used by a configurator tool to check the compatibility of the messages. Messages would be considered compatible if strings are either identical, or one is a prefix of the other in case of extended messages.
This configurator tool can be either a human-operated program, or an auto-configurator module running on system startup.

My hope is that taken together with the previous ID management proposal, this can get the usability and robustness of v1 to be equal or better than those of v0 .

Very rough POC code that generates the signature from DSDL can be seen here: https://gitlab.com/vadimz1/dsdl-sig-gen/-/blob/main/sig-gen.py

2 Likes

Thanks, Vadim.

This is a sensible proposal but substring matching is an unnecessarily limiting approach because it would yield a false-negative for the case where a nested struct in type A is a subtype of the nested struct in type B. Example:

# type A
X.1.0 nested
uint8 foo
# type B
Y.1.0 nested
uint8 foo
# type X
uint8 bar
@extent 16
# type Y (subtype of X)
uint8 bar
uint8 baz
@extent 16

In this example, A and B are compatible, but your approach would misidentify them as incompatible. One way to fix it is to implement recursive substring matching for nested delimited composites.

Indeed, I have not considered extended nested types possibility.
Since, unlike my original proposal, this POC has the nested type boundaries, recursive matching can be implemented.

1 Like

Looks like a good POC! @VadimZ @pavel.kirienko is this intended to be used alongside Vadim’s previous regarding public regulated default port-id tables, or is it a replacement?

In my view the proposals are complementary. Default port-id tables allow log analysis (which type-sig does not solve), and type signature reduce risk of totally incompatible messages being received, and will allow matching of the device during configuration phase (which port-id table does not solve)

actually seems like recursion is not even needed to support nested extension, see this example https://godbolt.org/z/48fsbbK8o

Nice.

Sealed nested composites should be differentiated from delimited nested composites because the former kind is non-extensible. Example:

# Outer type
X.1.0 nested
uint8 foo
# Inner type X is not extensible
uint7 bar
@sealed

If you encode this example as {u7}u8, it would not be possible to differentiate it from the case where X is not @sealed.

Merely omitting the brackets is probably not an acceptable idea because we need to respect the alignment. DSDL requires that all nested composites are aligned at 8 bits (1 byte), meaning that the X could be modified as follows while retaining compatibility:

# Inner type X is not extensible
uint7 bar
bool extra  # Same binary layout!
@sealed

Let’s assume that we use parens to represent sealed composites. Then we end up with the following signatures for the two examples above:

  • (u7)u8
  • (u7b1)u8

We should be able to recognize that they are compatible despite the nested composites being sealed and different.

Implemented and committed.

See https://godbolt.org/z/oY9oW5x8d for comparison function sandbox.

1 Like

Nice.

Here’s another interesting edge case, consider the old reg.drone.service.actuator.common.Feedback.0.1:

# reg.drone.service.actuator.common.Feedback.0.1
reg.drone.service.common.Heartbeat.0.1 heartbeat
int8 demand_factor_pct
@extent 63 * 8

It is a structural subtype (unless nested into another type; see section 3.4.5.7 Type polymorphism and equivalency) of reg.drone.service.common.Heartbeat.0.1, which is defined as follows:

# reg.drone.service.common.Heartbeat.0.1
reg.drone.service.common.Readiness.0.1 readiness
uavcan.node.Health.1.0 health
@sealed

Which in turn is a structural subtype of reg.drone.service.common.Readiness.0.1:

# reg.drone.service.common.Readiness.0.1
truncated uint2 value
@sealed

I imagine that the signature of the first type (Feedback) would look as follows:

((u2)(u2))i8

Where the first (u2) represents Readiness and the second one is for Health.

It is valid to subscribe to a subject of type Feedback using type Readiness, which is just u2. How do we express/detect such compatibility using your hash?


One rough idea that comes to mind (not sure it’s a good one) is to drop parens for @sealed composites and instead introduce an alignment operator, like =, that indicates that the current offset should be padded up to a specified number of bits. The first type then becomes:

u2=8u2=8i8

@VadimZ do you have a few minutes to update this thread per the current status of this proposal? I think we advanced this effort somewhat in our private exchange that needs to be reflected here.

I’ve pushed a commit to support signature generation for service types and tagged unions (Should cover all the data types in DSDL, unless I’m missing something again :slight_smile: )
The matcher still needs to be modified to support those two types. I’m somewhat short on time for the next two weeks, so can’t promise swift action …
Edit: Other than services and unions, the matcher should be functional and supports all the requirements discussed, though crowdsourced edge cases in form of unit test additions would be welcome.

2 Likes