Skip to content

Char[] / String handling is a bit broken? #370

Description

@azerupi

On the project I'm working on we are parsing MAVLink messages from PX4 and QGroundControl, intercepting some and forwarding the rest. While doing that I've noticed that some forwarded messages containing char[] fields are not byte equivalent before and after decode and re-encoding.

The MAVLink spec doesn't specify anything about NULL termination and each message seems to have its own specific requirements around it. Some examples:

  • AUTH_KEY (7): key: char[32] nothing specified. No null-termination as the key would always be 32 chars long.
  • PARAM_VALUE (22): param_id: char[16] terminated by NULL if the length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte if the length is exactly 16 chars
  • ADSB_VEHICLE (246): callsign: char[9] 8+null
  • STATUSTEXT (253): text: char[50] Status text message, without null termination character

It is not clear to me if ALL strings that are smaller than the char[] field length should be null-terminated or not?

In any case rust-mavlink does not handle this correctly. If we look at the PARAM_VALUE_DATA struct that is generated:

pub struct PARAM_VALUE_DATA {
    // omitted some fields...
    pub param_id: arrayvec::ArrayString<16>,
    // omitted some fields...
}

In the deserialization code we can see that if a null byte is encountered it will be truncated from the string.

for _ in 0..16usize {
    let next_char = buf.get_u8();
    if next_char == 0 {
        break;
    }
    __struct.param_id.push(next_char as char);
}

In the serialization however we are only adding values for as many bytes are in the slice. First this is bad because it means if the array/string does not contain exactly the amount of bytes that the field is expecting we are shifting everything left. Second when re-encoding the message we received from the deserialization step we've already lost the null-byte if it was there and thus we are always triggering the issue.

let slice = self.param_id.as_bytes();
for val in slice {
    __tmp.put_u8(*val);
}

Here is an example of the PARAM_VALUE message before deserialization and after deserialization + re-serialization in wireshark. You can see the null byte is missing and the length is one shorter.

Image

I can take a stab at fixing this, but I would first like to discuss what the right solution would be. Would the simple solution of zero filling the remaining bytes be the correct solution for all cases?

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind:bugSomething is broken or behaving incorrectly and needs a fix.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions