Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
buggmagnet committed Jun 12, 2024
1 parent 8e1a445 commit d76fa40
Show file tree
Hide file tree
Showing 11 changed files with 568 additions and 30 deletions.
28 changes: 24 additions & 4 deletions ios/MullvadPostQuantum/PostQuantumKeyNegotiator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,44 @@
import Foundation
import MullvadTypes
import NetworkExtension
import PacketTunnelCore
import TalpidTunnelConfigClientProxy
import WireGuardKitTypes

// swiftlint:disable function_parameter_count
public protocol PostQuantumKeyNegotiation {
func startNegotiation(
gatewayIP: IPv4Address,
devicePublicKey: PublicKey,
presharedKey: PrivateKey,
packetTunnel: any TunnelProvider,
tcpConnection: NWTCPConnection,
postQuantumKeyExchangeTimeout: Duration
) -> Bool

func cancelKeyNegotiation()

init()
}

/**
Attempt to start the asynchronous process of key negotiation. Returns true if successfully started, false if failed.
*/
public class PostQuantumKeyNegotiator {
public init() {}
public class PostQuantumKeyNegotiator: PostQuantumKeyNegotiation {
required public init() {}

var cancelToken: PostQuantumCancelToken?

public func startNegotiation(
gatewayIP: IPv4Address,
devicePublicKey: PublicKey,
presharedKey: PrivateKey,
packetTunnel: NEPacketTunnelProvider,
packetTunnel: any TunnelProvider,
tcpConnection: NWTCPConnection,
postQuantumKeyExchangeTimeout: Duration
) -> Bool {
let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel).toOpaque()
// swiftlint:disable:next force_cast
let packetTunnelPointer = Unmanaged.passUnretained(packetTunnel as! NEPacketTunnelProvider).toOpaque()
let opaqueConnection = Unmanaged.passUnretained(tcpConnection).toOpaque()
var cancelToken = PostQuantumCancelToken()

Expand Down Expand Up @@ -57,3 +75,5 @@ public class PostQuantumKeyNegotiator {
drop_post_quantum_key_exchange_token(&cancelToken)
}
}

// swiftlint:enable function_parameter_count
95 changes: 95 additions & 0 deletions ios/MullvadPostQuantumTests/MullvadPostQuantum+Stubs.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// MullvadPostQuantum+Stubs.swift
// MullvadPostQuantumTests
//
// Created by Marco Nikic on 2024-06-12.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

@testable import MullvadPostQuantum
@testable import MullvadTypes
import NetworkExtension
@testable import PacketTunnelCore
@testable import WireGuardKitTypes

// swiftlint:disable function_parameter_count
class NWTCPConnectionStub: NWTCPConnection {
var _isViable = false
override var isViable: Bool {
_isViable
}

func becomeViable() {
willChangeValue(for: \.isViable)
_isViable = true
didChangeValue(for: \.isViable)
}
}

class TunnelProviderStub: TunnelProvider {
let tcpConnection: NWTCPConnectionStub

init(tcpConnection: NWTCPConnectionStub) {
self.tcpConnection = tcpConnection
}

func createTCPConnectionThroughTunnel(
to remoteEndpoint: NWEndpoint,
enableTLS: Bool,
tlsParameters TLSParameters: NWTLSParameters?,
delegate: Any?
) -> NWTCPConnection {
tcpConnection
}
}

class FailedNegotiatorStub: PostQuantumKeyNegotiation {
var onCancelKeyNegotiation: (() -> Void)?

required init() {
onCancelKeyNegotiation = nil
}

init(onCancelKeyNegotiation: (() -> Void)? = nil) {
self.onCancelKeyNegotiation = onCancelKeyNegotiation
}

func startNegotiation(
gatewayIP: IPv4Address,
devicePublicKey: WireGuardKitTypes.PublicKey,
presharedKey: WireGuardKitTypes.PrivateKey,
packetTunnel: PacketTunnelCore.TunnelProvider,
tcpConnection: NWTCPConnection,
postQuantumKeyExchangeTimeout: MullvadTypes.Duration
) -> Bool { false }

func cancelKeyNegotiation() {
onCancelKeyNegotiation?()
}
}

class SuccessfulNegotiatorStub: PostQuantumKeyNegotiation {
var onCancelKeyNegotiation: (() -> Void)?
required init() {
onCancelKeyNegotiation = nil
}

init(onCancelKeyNegotiation: (() -> Void)? = nil) {
self.onCancelKeyNegotiation = onCancelKeyNegotiation
}

func startNegotiation(
gatewayIP: IPv4Address,
devicePublicKey: WireGuardKitTypes.PublicKey,
presharedKey: WireGuardKitTypes.PrivateKey,
packetTunnel: PacketTunnelCore.TunnelProvider,
tcpConnection: NWTCPConnection,
postQuantumKeyExchangeTimeout: MullvadTypes.Duration
) -> Bool { true }

func cancelKeyNegotiation() {
onCancelKeyNegotiation?()
}
}

// swiftlint:enable function_parameter_count
91 changes: 91 additions & 0 deletions ios/MullvadPostQuantumTests/PostQuantumKeyExchangeActorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// PostQuantumKeyExchangeActorTests.swift
// PostQuantumKeyExchangeActorTests
//
// Created by Marco Nikic on 2024-06-12.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

@testable import MullvadPostQuantum
@testable import MullvadTypes
import NetworkExtension
@testable import PacketTunnelCore
@testable import WireGuardKitTypes
import XCTest

class MullvadPostQuantumTests: XCTestCase {
var tcpConnection: NWTCPConnectionStub!
var tunnelProvider: TunnelProviderStub!

override func setUpWithError() throws {
tcpConnection = NWTCPConnectionStub()
tunnelProvider = TunnelProviderStub(tcpConnection: tcpConnection)
}

func testKeyExchangeFailsWhenNegotiationCannotStart() {
let negotiationFailure = expectation(description: "Negotiation failed")

let keyExchangeActor = PostQuantumKeyExchangeActor(
packetTunnel: tunnelProvider,
onFailure: {
negotiationFailure.fulfill()
},
negotiationProvider: FailedNegotiatorStub.self
)

let privateKey = PrivateKey()
keyExchangeActor.startNegotiation(with: privateKey)
tcpConnection.becomeViable()

wait(for: [negotiationFailure])
}

func testKeyExchangeFailsWhenTCPConnectionIsNotReadyInTime() {
let negotiationFailure = expectation(description: "Negotiation failed")

// Setup the actor to wait only 10 milliseconds before declaring the TCP connection is not ready in time.
let keyExchangeActor = PostQuantumKeyExchangeActor(
packetTunnel: tunnelProvider,
onFailure: {
negotiationFailure.fulfill()
},
negotiationProvider: FailedNegotiatorStub.self,
keyExchangeRetriesIterator: AnyIterator { .milliseconds(10) }
)

let privateKey = PrivateKey()
keyExchangeActor.startNegotiation(with: privateKey)

wait(for: [negotiationFailure])
}

func testResetEndsTheCurrentNegotiation() throws {
let unexpectedNegotiationFailure = expectation(description: "Unexpected negotiation failure")
unexpectedNegotiationFailure.isInverted = true

let keyExchangeActor = PostQuantumKeyExchangeActor(
packetTunnel: tunnelProvider,
onFailure: {
unexpectedNegotiationFailure.fulfill()
},
negotiationProvider: SuccessfulNegotiatorStub.self
)

let privateKey = PrivateKey()
keyExchangeActor.startNegotiation(with: privateKey)

let negotiationProvider = try XCTUnwrap(
keyExchangeActor.negotiation?
.negotiator as? SuccessfulNegotiatorStub
)

let negotationCancelledExpectation = expectation(description: "Negotiation cancelled")
negotiationProvider.onCancelKeyNegotiation = {
negotationCancelledExpectation.fulfill()
}

keyExchangeActor.reset()

wait(for: [negotationCancelledExpectation, unexpectedNegotiationFailure], timeout: 0.5)
}
}
Loading

0 comments on commit d76fa40

Please sign in to comment.