Skip to content

Commit b2f8f35

Browse files
madsodgaardweissiLukasa
authored
Implement bestEffort functions for hashable and equatable. (#280)
* Make TLSConfiguration Hashable * Fix equatable conformance * Add basic tests * Remove hash not equal check * Implement bestEffort functions * add doc * add test for different callbacks * generate tests Co-authored-by: Johannes Weiss <[email protected]> Co-authored-by: Cory Benfield <[email protected]>
1 parent 597f4ac commit b2f8f35

File tree

3 files changed

+84
-3
lines changed

3 files changed

+84
-3
lines changed

Sources/NIOSSL/TLSConfiguration.swift

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,20 @@ public enum TLSVersion {
2828
}
2929

3030
/// Places NIOSSL can obtain certificates from.
31-
public enum NIOSSLCertificateSource {
31+
public enum NIOSSLCertificateSource: Hashable {
3232
@available(*, deprecated, message: "Use 'NIOSSLCertificate.fromPEMFile(_:)' to load the certificate(s) and use the '.certificate(NIOSSLCertificate)' case to provide them as a source")
3333
case file(String)
3434
case certificate(NIOSSLCertificate)
3535
}
3636

3737
/// Places NIOSSL can obtain private keys from.
38-
public enum NIOSSLPrivateKeySource {
38+
public enum NIOSSLPrivateKeySource: Hashable {
3939
case file(String)
4040
case privateKey(NIOSSLPrivateKey)
4141
}
4242

4343
/// Places NIOSSL can obtain a trust store from.
44-
public enum NIOSSLTrustRoots {
44+
public enum NIOSSLTrustRoots: Hashable {
4545
/// Path to either a file of CA certificates in PEM format, or a directory containing CA certificates in PEM format.
4646
///
4747
/// If a path to a file is provided, the file can contain several CA certificates identified by
@@ -507,3 +507,58 @@ public struct TLSConfiguration {
507507
additionalTrustRoots: additionalTrustRoots)
508508
}
509509
}
510+
511+
// MARK: BestEffortHashable
512+
extension TLSConfiguration {
513+
/// Returns a best effort result of whether two `TLSConfiguration` objects are equal.
514+
///
515+
/// The "best effort" stems from the fact that we are checking the pointer to the `keyLogCallback` closure.
516+
///
517+
/// - warning: You should probably not use this function. This function can return false-negatives, but not false-positives.
518+
public func bestEffortEquals(_ comparing: TLSConfiguration) -> Bool {
519+
let isKeyLoggerCallbacksEqual = withUnsafeBytes(of: self.keyLogCallback) { callbackPointer1 in
520+
return withUnsafeBytes(of: comparing.keyLogCallback) { callbackPointer2 in
521+
return callbackPointer1.elementsEqual(callbackPointer2)
522+
}
523+
}
524+
525+
return self.minimumTLSVersion == comparing.minimumTLSVersion &&
526+
self.maximumTLSVersion == comparing.maximumTLSVersion &&
527+
self.cipherSuites == comparing.cipherSuites &&
528+
self.verifySignatureAlgorithms == comparing.verifySignatureAlgorithms &&
529+
self.signingSignatureAlgorithms == comparing.signingSignatureAlgorithms &&
530+
self.certificateVerification == comparing.certificateVerification &&
531+
self.trustRoots == comparing.trustRoots &&
532+
self.certificateChain == comparing.certificateChain &&
533+
self.privateKey == comparing.privateKey &&
534+
self.applicationProtocols == comparing.applicationProtocols &&
535+
self.encodedApplicationProtocols == comparing.encodedApplicationProtocols &&
536+
self.shutdownTimeout == comparing.shutdownTimeout &&
537+
isKeyLoggerCallbacksEqual &&
538+
self.renegotiationSupport == comparing.renegotiationSupport
539+
}
540+
541+
/// Returns a best effort hash of this TLS configuration.
542+
///
543+
/// The "best effort" stems from the fact that we are hashing the pointer bytes of the `keyLogCallback` closure.
544+
///
545+
/// - warning: You should probably not use this function. This function can return false-negatives, but not false-positives.
546+
public func bestEffortHash(into hasher: inout Hasher) {
547+
hasher.combine(minimumTLSVersion)
548+
hasher.combine(maximumTLSVersion)
549+
hasher.combine(cipherSuites)
550+
hasher.combine(verifySignatureAlgorithms)
551+
hasher.combine(signingSignatureAlgorithms)
552+
hasher.combine(certificateVerification)
553+
hasher.combine(trustRoots)
554+
hasher.combine(certificateChain)
555+
hasher.combine(privateKey)
556+
hasher.combine(applicationProtocols)
557+
hasher.combine(encodedApplicationProtocols)
558+
hasher.combine(shutdownTimeout)
559+
withUnsafeBytes(of: keyLogCallback) { closureBits in
560+
hasher.combine(bytes: closureBits)
561+
}
562+
hasher.combine(renegotiationSupport)
563+
}
564+
}

Tests/NIOSSLTests/TLSConfigurationTest+XCTest.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ extension TLSConfigurationTest {
4343
("testNonexistentFileObject", testNonexistentFileObject),
4444
("testComputedApplicationProtocols", testComputedApplicationProtocols),
4545
("testKeyLogManagerOverlappingAccess", testKeyLogManagerOverlappingAccess),
46+
("testTheSameHashValue", testTheSameHashValue),
47+
("testDifferentHashValues", testDifferentHashValues),
48+
("testDifferentCallbacksNotEqual", testDifferentCallbacksNotEqual),
4649
]
4750
}
4851
}

Tests/NIOSSLTests/TLSConfigurationTest.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,27 @@ class TLSConfigurationTest: XCTestCase {
496496
group.wait()
497497
XCTAssertEqual([true, true], completionsQueue.sync { completions })
498498
}
499+
500+
func testTheSameHashValue() {
501+
let config = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake.file"), applicationProtocols: ["http/1.1"])
502+
let theSameConfig = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake.file"), applicationProtocols: ["http/1.1"])
503+
var hasher = Hasher()
504+
var hasher2 = Hasher()
505+
config.bestEffortHash(into: &hasher)
506+
theSameConfig.bestEffortHash(into: &hasher2)
507+
XCTAssertEqual(hasher.finalize(), hasher2.finalize())
508+
XCTAssertTrue(config.bestEffortEquals(theSameConfig))
509+
}
510+
511+
func testDifferentHashValues() {
512+
let config = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake.file"), applicationProtocols: ["http/1.1"])
513+
let differentConfig = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake2.file"), applicationProtocols: ["http/1.1"])
514+
XCTAssertFalse(config.bestEffortEquals(differentConfig))
515+
}
516+
517+
func testDifferentCallbacksNotEqual() {
518+
let config = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake.file"), applicationProtocols: ["http/1.1"], keyLogCallback: { _ in })
519+
let differentConfig = TLSConfiguration.forServer(certificateChain: [], privateKey: .file("fake.file"), applicationProtocols: ["http/1.1"], keyLogCallback: { _ in })
520+
XCTAssertFalse(config.bestEffortEquals(differentConfig))
521+
}
499522
}

0 commit comments

Comments
 (0)