dbus2 / zbus

Rust D-Bus crate.
Other
316 stars 70 forks source link

zvariant: Inconsistent serialization of varible key length dictionary contained within `zvariant::Value` #868

Closed felinira closed 1 week ago

felinira commented 1 week ago

The following test code uses glib-rs to construct a VARIANT type gvariant with glib::Variant::from_variant and the same variant with zvariant::Value in gvariant mode.

    #[test]
    fn gvariant_vs_zvariant() {
        let mut map_glib = std::collections::HashMap::<&str, &str>::new();
        map_glib.insert("k", "v");
        let variant_glib = glib::Variant::from_variant(&map_glib.to_variant()).normal_form();
        let data_glib = variant_glib.data();

        let mut map_zvariant = std::collections::HashMap::<&str, &str>::new();
        map_zvariant.insert("k", "v".into());
        let ctxt = zvariant::serialized::Context::new_gvariant(zvariant::LE, 0);

        let data_zvariant = zvariant::to_bytes(ctxt, &zvariant::Value::new(map_zvariant)).unwrap();
        //let data_zvariant = zvariant::to_bytes(ctxt, &zvariant::SerializeValue(&map_zvariant)).unwrap();

        assert_eq!(data_glib, &*data_zvariant, "gvariant vs zvariant");
    }

This is a byte comparison of the serialisation. Left is glib, right is zvariant:

Diff < left / right > :
< 00000000  6B 00 76 00  02 05 00 61  7B 73 73 7D               k.v....a{ss}
> 00000000  6B 00 76 00  04 00 61 7B  73 73 7D                  k.v...a{ss}

It looks like zvariant does not encode the key framing offset, while glib does. https://developer.gnome.org/documentation/specifications/gvariant-specification-1.0.html#dictionary-entries.

Dictionary entries are treated as structures with exactly two items — first the key, then the value. In the case that the key is fixed-sized, there will be no framing offsets, and in the case the key is non-fixed-size there will be exactly one. As the value is treated as the last item in the structure, it will never have a framing offset.

glib will look for the framing offset, read the dict entry offset instead, and then fail to parse the entry correctly.

This is a comparison of glib::Variant::print on these variants, for completion sake:

Diff < left / right > :
<<{'k': 'v'}>
><{'': ''}>

Replacing zvariant::Value::new with zvariant::SerializeValue fixes the serialisation issue. However this is not always possible, especially when having to store variants in unserialized form, or when nesting variants.

zeenix commented 1 week ago

Replacing zvariant::Value::new with zvariant::SerializeValue fixes the serialisation issue.

That's very strange. :thinking: The encoding of both should be exactly the same.

I'm afraid I won't have the time to investigate and fix this anytime soon. PRs welcome of course.