Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🩹 🚸 zv: Equal Signatures irrespective of D-Bus marshalled state #381

Merged
merged 3 commits into from
Sep 29, 2023

Conversation

luukvanderduim
Copy link
Contributor

@luukvanderduim luukvanderduim commented Jun 13, 2023

This adjusts the PartialEq impls to evaluate equality irrespective of
of marshalling of the type.

Two signatures representing the same type can differ as a result of
D-Bus marshalling. D-Bus marshalling will omit outer parentheses from body
singatures.

Consider a MyType { a: i32, b: i32}:

MyType::signature() returns a "(ii)" Signature whereas
Message::body_signature() with MyType as body, would return
a "ii" Signature, which may lead to unexpected inequality of Signatures.

Note: PartialEq on Signature accounts for marshalling only.
One can construct- or deserialize into deeper nested structs or tuples than
than the signature describes but at that point the divergence is expected.

@luukvanderduim luukvanderduim marked this pull request as ready for review June 13, 2023 19:12
@luukvanderduim luukvanderduim marked this pull request as draft June 13, 2023 19:56
@luukvanderduim luukvanderduim force-pushed the disregard-outer-parens-signature branch 3 times, most recently from 959ce71 to 5fe0d63 Compare June 16, 2023 16:24
@luukvanderduim luukvanderduim changed the title [zv] Signature equality regardless of outer parentheses. 🩹 🚸 zv: Equal Signatures regardless of outer parentheses / marshalled state Jun 17, 2023
@luukvanderduim luukvanderduim changed the title 🩹 🚸 zv: Equal Signatures regardless of outer parentheses / marshalled state 🩹 🚸 zv: Equal Signatures irrespective of D-Bus marshalled state Jun 17, 2023
@luukvanderduim luukvanderduim marked this pull request as ready for review June 20, 2023 11:20
@luukvanderduim luukvanderduim force-pushed the disregard-outer-parens-signature branch 4 times, most recently from 9de8ab1 to c1456d4 Compare June 21, 2023 12:44
Copy link
Contributor

@zeenix zeenix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good otherwise. Also, it would be great to modify zbus code to make use of this in message-handling code.

zvariant/src/utils.rs Outdated Show resolved Hide resolved
zvariant/src/utils.rs Outdated Show resolved Hide resolved
zvariant/src/signature.rs Outdated Show resolved Hide resolved
zvariant/src/utils.rs Outdated Show resolved Hide resolved
zvariant/src/signature.rs Show resolved Hide resolved
zvariant/src/signature.rs Outdated Show resolved Hide resolved
zvariant/src/signature.rs Outdated Show resolved Hide resolved
zvariant/src/signature.rs Outdated Show resolved Hide resolved
@luukvanderduim luukvanderduim force-pushed the disregard-outer-parens-signature branch 4 times, most recently from 135d1d9 to 8631aa6 Compare June 21, 2023 19:30
zvariant/src/signature.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@zeenix zeenix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand you're still grasping around git rebasing etc so I understand completely (I'd make a lot more mistakes in your shoes for sure) but kindly review each line of every commit before (or after) pushing) to catch such mistakes.

@luukvanderduim
Copy link
Contributor Author

Unfortunately I have messed up. I will take your advise to heart, sorry to trouble you with it.

@luukvanderduim luukvanderduim force-pushed the disregard-outer-parens-signature branch 3 times, most recently from 40ef548 to 2e5af75 Compare June 26, 2023 10:12
@luukvanderduim
Copy link
Contributor Author

More comfortable with the commits' outline, but shape is just a prerequisite

I remember you mention a preference for string comparisons in favor of the byte slice comparisons.
This is what I ended up with:

    fn eq(&self, other: &Signature<'_>) -> bool {
        match (
            self.without_outer_parentheses(),
            other.without_outer_parentheses(),
        ) {
            (Some(lhs_without), None) => *lhs_without == **other,
            (None, Some(rhs_without)) => **self == *rhs_without,
            _ => **self == **other,
        }
    }

I am a bit torn, as the dereferences do different things and make it less clear what actually happens but on the other hand, this is prettier than having six .as_bytes() calls around, and the alternative of having six .as_str() about the same.
I am sure you know what you like better.

@zeenix
Copy link
Contributor

zeenix commented Jun 27, 2023

More comfortable with the commits' outline, but shape is just a prerequisite

👍

I remember you mention a preference for string comparisons in favor of the byte slice comparisons. This is what I ended up with:

hmm..

  • why can't without_outer_parentheses method always return a slice? If there are no parantheses, it just returns the input as is.
  • I wasn't objecting to use of as_bytes but rather byte literals because of b' suffix. Looks a bit ugly so when there is no need, I'd very much like to avoid it.

@luukvanderduim luukvanderduim force-pushed the disregard-outer-parens-signature branch from 2e5af75 to e0f9fc1 Compare June 27, 2023 16:48
@luukvanderduim
Copy link
Contributor Author

luukvanderduim commented Jun 27, 2023

* why can't `without_outer_parentheses` method always return a slice? If there are no parantheses, it just returns the input as is.

It can - and it does now.
This simplifies PartialEq nicely.

Now, I am pondering whether the method without_outer_parentheses should be called either marshall_signature or just 'marshalled_str' because it is not necessarily removing outer parentheses.

I misunderstood how Signature::slice is not intended to hand out a slice of its bytes.
I see now how ``sliceupdatespos` and `end` and Signature::as_str` does do the slicing.
I presume this is a more widely used pattern I had just not looked at this closely before.

as_marshalled_str?

@luukvanderduim
Copy link
Contributor Author

Being curious what the differences would be between the approaches, I rolled a quick bench crate

@zeenix
Copy link
Contributor

zeenix commented Jun 28, 2023

* why can't `without_outer_parentheses` method always return a slice? If there are no parantheses, it just returns the input as is.

It can - and it does now.
This simplifies PartialEq nicely.

Nice. 👍

Now, I am pondering whether the method without_outer_parentheses should be called either marshall_signature or just 'marshalled_str' because it is not necessarily removing outer parentheses.

Nah, without_outer_parentheses is fine. Just add doc string to the method, explaining that it doesn't remove parens if there are none.

@zeenix
Copy link
Contributor

zeenix commented Jun 28, 2023

You mentioned zbus earlier but I think I read 'could' then.

I wrote would. :) But not worries.

I take it that this refers to MessageBuilder?

Yes (at least, see if we do something like that elsewhere too in code please).

@luukvanderduim luukvanderduim force-pushed the disregard-outer-parens-signature branch from e0f9fc1 to 280296b Compare June 28, 2023 23:06
@luukvanderduim luukvanderduim marked this pull request as draft June 28, 2023 23:18
@luukvanderduim luukvanderduim force-pushed the disregard-outer-parens-signature branch from 280296b to 057d770 Compare June 29, 2023 13:54
@luukvanderduim luukvanderduim marked this pull request as ready for review June 29, 2023 20:48
Copy link
Contributor

@zeenix zeenix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually apart from these minor things, I don't see why we can't merge this work. Am I forgetting something from our conversations on Matrix? 🤔

You'll have to rebase on main in any case.

zbus/src/message_builder.rs Outdated Show resolved Hide resolved
zvariant/src/signature.rs Outdated Show resolved Hide resolved
@luukvanderduim
Copy link
Contributor Author

Funny we end up in this situation where you suggest to merge and I have concerns.

On matrix I shared that I had come to realize that R (the Rust type), B (the Message body type), and D (the de-serialized Rust type) are not necessarily the same, despite being closely related. They share "a list of zero or more single complete types, each made up of one or more type codes" (in D-Bus spec speak) - but may differ by zero or more outer structs or tuples.

An "ii" body deserializes to "(((ii)))" just fine, which is understandable, because D-Bus and zvariant don't care about the outer shape, and happily accommodate how the user wants the data. But if some user does this, we are more than one pair of parentheses apart

The question is, should PartialEq reflect on equality of the underlying data and be as lenient as going 'R->B->D' can be?, Or should it support the common case of allowing divergence by one pair of parentheses, as this PR?
Or should it just stick to being a trait to a type and tell it like it is?

The first way offers least chance of surprise and most expensive PartialEq , second, this PR, is pragmatic but does not cover all cases, the last seems most principled and is least expensive.

In tests, people probably will not care about more expensive equality.
Users might care if they check signatures in a hot loop?
Could SignalStream::filter_map by signature ,ale sense for some people, for example?

Either way, I do think the associated functions on Signature make sense.

@zeenix
Copy link
Contributor

zeenix commented Aug 9, 2023

Funny we end up in this situation where you suggest to merge and I have concerns.

😆

On matrix I shared that I had come to realize that R (the Rust type), B (the Message body type), and D (the de-serialized Rust type) are not necessarily the same

Right, I recall now. :) As I tried to explain, I do not care much for theoretical issues. We already need to treat (x) the same as x and we can't help it, and in theory that's just wrong. So I'm more concerned about consistency here, above all.

Or should it support the common case of allowing divergence by one pair of parentheses, as this PR?

Yeah, I think this is a good compromise. Hence why I'm thinking this PR is the way to go.

second, this PR, is pragmatic but does not cover all cases

I don't think we need to cover all cases. You'll notice that message::Builder removes only one layer of (). So this approach is also consistent with what we've been doing already.

@zeenix
Copy link
Contributor

zeenix commented Aug 10, 2023

@luukvanderduim You've more than a 100 commits on the PR now. Something went horribly wrong in your rebase. :(

@luukvanderduim
Copy link
Contributor Author

Unfortunate.
Ill go through the motions again.

@luukvanderduim luukvanderduim force-pushed the disregard-outer-parens-signature branch 2 times, most recently from b84fdf6 to 7bab9a7 Compare August 11, 2023 09:25
@luukvanderduim
Copy link
Contributor Author

Sanity seems to have been restored. We are back at three commits, which - looking at the changes - are what we want.
One commit dropped because the function it modified no longer exists.

@zeenix
Copy link
Contributor

zeenix commented Aug 11, 2023

Sanity seems to have been restored. We are back at three commits, which - looking at the changes - are what we want.

Great, thanks again for still not giving up. :)

One commit dropped because the function it modified no longer exists.

It exists in another form (see commit a0b92da). Please make sure the new code doesn't need these changes that were dropped.

Btw, I think the CI can be fixed by just rebasing on the latest main branch.

@luukvanderduim
Copy link
Contributor Author

luukvanderduim commented Aug 11, 2023

One commit dropped because the function it modified no longer exists.

It exists in another form (see commit a0b92da). Please make sure the new code doesn't need these changes that were dropped.

Looks like it does:

SignatureParser::validate() relies on SignatureParser::from_bytes_unchecked()
Checks max. length is <= 255.

SignatureParser::validate() then relies on SignatureParser's Iterator implementation.

SignatureParser as Iterator::next() ->
None if self.done(), otherrwise:

next_signature() parses by (start-) character and if it finds an opening parenthesis,
it calls next_structure_signature():

Which errors when it would find an empty struct.
Initializes a 'field parser' and keeps track of fields' lengths.
The while loop advances over the field_parser slice, calling parse_next_signature
and sums up the lengths of the 'single complete types' / struct field signatures it finds, until
either at premature end or when it finds ')'

  • Would Err it next_char were not ')' (because it would mean the loop exited prematurely.

    returns Ok(Signature) signature of slice of the struct.
    caller next_signature() returns Ok(Signature)
    caller next() returns Some(Ok(Signature))

validate() evaluates these 'single complete type signature' results: s?.

Long story short, yes I believe 'balanced parantheses' are checked this way.

Note, there are things the parser does not do, but also is not that important:

  • max. braces depth (32), max. total nesting depth (64)

Btw, I think the CI can be fixed by just rebasing on the latest main branch.

Seems #440 is related.

@zeenix
Copy link
Contributor

zeenix commented Aug 14, 2023

Long story short, yes I believe 'balanced parantheses' are checked this way.

👍 so these changes are good then, good.

Btw, I think the CI can be fixed by just rebasing on the latest main branch.

Seems #440 is related.

Right, it should work now. I restarted the failing job. 🤞

max. braces depth (32), max. total nesting depth (64)

Hmm.. currently these are checked by the serializers and deserializers so it is ultimately checked. However, now I wonder if the checks should be on Signature creation itself. The signature is parsed for checking anyway.

@luukvanderduim luukvanderduim force-pushed the disregard-outer-parens-signature branch from 7bab9a7 to 89b42ad Compare August 14, 2023 13:45
@luukvanderduim
Copy link
Contributor Author

Right, it should work now. I restarted the failing job. crossed_fingers

Rebased to main.

max. braces depth (32), max. total nesting depth (64)

Hmm.. currently these are checked by the serializers and deserializers so it is ultimately checked. However, now I wonder if the checks should be on Signature creation itself. The signature is parsed for checking anyway.

Does moving those checks solve a problem?

@zeenix
Copy link
Contributor

zeenix commented Aug 14, 2023

Hmm.. currently these are checked by the serializers and deserializers so it is ultimately checked. However, now I wonder if the checks should be on Signature creation itself. The signature is parsed for checking anyway.

Does moving those checks solve a problem?

Well ser/de process isn't currently as fast as it should be so maybe it would help there. Signature creation is typically amortized. So it'll definitely optimize the overall code but can't be sure if it'd be significant enough to care.

Copy link
Contributor

@zeenix zeenix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, i still found something not right. :)

zvariant/src/signature.rs Outdated Show resolved Hide resolved
zvariant/src/signature.rs Outdated Show resolved Hide resolved
@luukvanderduim luukvanderduim force-pushed the disregard-outer-parens-signature branch from 89b42ad to fc9acaf Compare August 14, 2023 15:57
This commit adjusts the `PartialEq` impl to evaluate equality, even if the type
was marshalled and has seen outer parentheses stripped.

Two signatures representing the same type can differ as a result of
D-Bus marshalling. D-Bus marshalling will omit outer parentheses from body
singatures.

Consider a `MyType { a: i32, b: i32}`:

`MyType::signature()` returns a "(ii)" `Signature` whereas
`Message::body_signature()` with `MyType` as body, would return
a "ii" `Signature`, which may lead to unexpected inequality of `Signature`s.

Note: `PartialEq` on `Signature` accounts for marshalling only.
One can construct- or deserialize into deeper nested structs or tuples than
than the signature describes but at that point the divergence is expected.
Adds test for `PartialEq` on Signature.
The test verifies if equality is correctly evaluated, both with and without
marshalled `Signature`s, as marshalling omits outer parentheses.
As per [`Eq`](https://doc.rust-lang.org/stable/std/cmp/trait.Eq.html#derivable)
docs on deriving: "Note that the derive strategy requires all fields are `Eq,
 which isn’t always desired."

We specify conditional `Signature` equality that does not include all
fields are (derivable) `Eq`.

This is why `Eq` for `Signature` is manually implemented.
@luukvanderduim
Copy link
Contributor Author

luukvanderduim commented Sep 29, 2023

Moved the associated functions out. I hope you are happy with where they landed (just above where they are needed, above impl PartialEq<Signature<>> for Signature<> and take note of the lifetime bounds on without_outer_parentheses which are new and are necessary to convey the relation between the borrow of the Signature and its internal reference.

@zeenix zeenix merged commit 3414eb0 into dbus2:main Sep 29, 2023
7 checks passed
@luukvanderduim luukvanderduim deleted the disregard-outer-parens-signature branch September 29, 2023 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants