-
Notifications
You must be signed in to change notification settings - Fork 151
Implement bestEffort functions for hashable and equatable. #280
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
Conversation
Can one of the admins verify this patch? |
11 similar comments
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
Can one of the admins verify this patch? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. That’s a good start! We’ll need to fix the equals method and need some tests too.
@swift-nio-bot test this please |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm uncertain about whether it's a good idea to define equatability and hashability for this object in a way that does not include the key log callback. This seems likely to cause surprising behaviours in cases where the type is used as a key.
I'm inclined to suggest we should solve the problem in async-http-client a different way, rather than using this incomplete and surprising definition for equatability and hashability.
What are the alternative ways of solving this problem? If |
Use something that isn't In this case it seems like if we wanted to do the least effort possible we could just put these definitions on a wrapper structure whose equality is defined this way. However, a better move is probably to have AHC have its own notion of TLS configuration not directly tied to swift-nio-ssl's, not least given that it works with multiple TLS backends anyway. |
@Lukasa / @madsodgaard I agree that we should only hash in here if we can include all callbacks, we definitely can't just compare everything but the callbacks. If the callbacks are all C callbacks, then I'm happy comparing their pointer address. Cory wdyt? @madsodgaard what Cory suggests is what I meant with if we can't reasonably hash it in here, then we can hack around it in AHC. The simplest way (in AHC) is |
@weissi Oh okay. And then it would be fine that we don't include the callbacks in AHC's definition of what |
@madsodgaard tbh, I don't think we can just not hash the callbacks at all. There can conceivably two distinct |
The callbacks are not C callbacks, they're Swift callbacks. |
@Lukasa / @madsodgaard Okay, then we have basically one option: hash/compare the bytes of the closure withUnsafeBytes(of: &self.theClosure) { closureBits in
var hasher = Hasher()
closureBits.forEach { hasher.combine($0) }
} @Lukasa wdyt? |
I remain uncertain of why we think we have to do this, for two reasons. Firstly, AHC has already made the mistake of trying to support two TLS implementations using the configuration for only one of them. This is a bad decision, and we shouldn't propagate it further. AHC should have a general TLS configuration object that it can use to derive the specific ones, and that object absolutely can be hashable. Secondly, even if we disregarded that, there is only one callback on |
The upsides of doing it in NIOSSL are:
One other option of doing it inside NIOSSL is to instead of implementing extension TLSConfiguration {
// will say no to two unequals, tries to say yes to two equals but can't guarantee no false negatives
public func bestEffortIsEqualTo(_ other: TLSConfiguration) -> Bool {
...
}
// two objects that are equal wrt `bestEffortIsEqualTo` are guaranteed to yield the same hash, others do not.
public func bestEffortHash(into: inout Hasher) -> UInt64 {
...
}
} WDYT?
Fair enough, @madsodgaard in that case I'd suggest to move this code into an internal struct HashableTLSConfiguration {
var tlsConfiguration: TLSConfiguration
}
extension HashableTLSConfiguration: Hashable {
...
} and then inside AHC, we'd just put a If @Lukasa thinks the |
friendly ping @Lukasa on the |
I'm not sure that we gain much from the |
But how are the dependencies supposed to hash/compare the right things? We'll add things to the TLS config in the future and then things will start to subtle break. So I think NIOSSL can definitely provide the best fidelity here (and IMHO should). But I agree with you that we shouldn't do it with |
The question remains why dependencies are hashing the TLS configuration at all. |
For connection pools, we need to be able to compare the TLS connection of the connection in the pool with the TLS connection that the user would like for the request. False negatives are fine, we can always create a new connection. That means we need (best effort) comparison (so the user can create an internal type wrapping the TLS config that adds |
Yes, I understand the proposed design. My question is whether that proposed design is good. Specifically, should libraries encapsulating swift-nio-ssl pervasively expose |
I see! I agree with you here.
Absolutely. I do however think that evangelism and not preventing the best effort eq/hash APIs is the best way to get there. Fact is that there are libraries that use This is compounded by the fact that creating a TLS configuration object that can then yield a Short of now offering connection pools, they only really replicate the eq/hash functionality in their own packages which I think may be at a risk of security vulnerabilities because we may add properties that they miss. That could make their hand-rolled eq/hash functionality have false positives (claiming two TLSConfigs are equal when they're not) and that's bad. |
I think if we think we should offer this, then we should go whole hog and use |
Sorry, ignore the above. I'm happy with this. I thought you meant |
/// | ||
/// - warning: You should probably not use this function. This function can return false-negatives, but not false-positives. | ||
public func bestEffortEquals(_ comparing: TLSConfiguration) -> Bool { | ||
let isKeyLoggerCallbacksEqual = withUnsafeBytes(of: self.keyLogCallback) { callbackPointer1 in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
given SR-14520 we should probably make this
let isBitEqual = withUnsafeBytes(of: self) { selfBits in
withUnsafeBytes(of: comparing) { otherBits in
return selfBits.elementsEqual(otherBits)
}
}
with a comment liking the bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lukasa wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think we should stick with the closure-based version, but yes agree we should link the bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lukasa the problem is that if we do hit the bug, then they will never be equal. The allocations are for reabstraction thunks which aren't equal because they capture (the heap allocs) :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! That looks good to me!
@swift-nio-bot add to allowlist |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you run the generate_linux_tests.rb
script? That's part of the issues we're seeing in CI: we're missing your new tests on Linux.
@Lukasa Done! |
@Lukasa this is now all green, the failure is the new 5.5 job which doesn't have docker images yet ;) |
This PR adds
bestEffortHash
andbestEffortEquals
in order to provide best effort method for usingHashable
andEquatable
in other projects. This is useful/needed to solve a problem such as swift-server/async-http-client#330