summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorVPNConnection.swift
blob: 82f18461a1a4be0237abdf6ae132b39e44e87e98 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//
//  SimulatorVPNConnection.swift
//  MullvadVPN
//
//  Created by Jon Petersson on 2023-09-07.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

#if targetEnvironment(simulator)

    import Foundation
    import MullvadREST
    import NetworkExtension

    class SimulatorVPNConnection: NSObject, VPNConnectionProtocol, @unchecked Sendable {
        // Protocol configuration is automatically synced by `SimulatorTunnelInfo`
        var protocolConfiguration = NEVPNProtocol()

        private let lock = NSRecursiveLock()
        private var _status: NEVPNStatus = .disconnected
        private var _reasserting = false
        private var _connectedDate: Date?

        private(set) var status: NEVPNStatus {
            get {
                lock.lock()
                defer { lock.unlock() }

                return _status
            }
            set {
                lock.lock()

                if _status != newValue {
                    _status = newValue

                    // Send notification while holding the lock. This should enable the receiver
                    // to fetch the `SimulatorVPNConnection.status` before the concurrent code gets
                    // opportunity to change it again.
                    postStatusDidChangeNotification()
                }

                lock.unlock()
            }
        }

        var reasserting: Bool {
            get {
                lock.lock()
                defer { lock.unlock() }

                return _reasserting
            }
            set {
                lock.lock()

                if _reasserting != newValue {
                    _reasserting = newValue

                    if newValue {
                        status = .reasserting
                    } else {
                        status = .connected
                    }
                }

                lock.unlock()
            }
        }

        private(set) var connectedDate: Date? {
            get {
                lock.lock()
                defer { lock.unlock() }

                return _connectedDate
            }
            set {
                lock.lock()
                _connectedDate = newValue
                lock.unlock()
            }
        }

        func startVPNTunnel() throws {
            try startVPNTunnel(options: nil)
        }

        func startVPNTunnel(options: [String: NSObject]?) throws {
            SimulatorTunnelProvider.shared.delegate.connection = self

            status = .connecting

            SimulatorTunnelProvider.shared.delegate.startTunnel(options: options) { error in
                if error == nil {
                    self.status = .connected
                    self.connectedDate = Date()
                } else if error is NoRelaysSatisfyingConstraintsError {
                    self.reasserting = true
                    self.connectedDate = nil
                } else {
                    self.status = .disconnected
                    self.connectedDate = nil
                }
            }
        }

        func stopVPNTunnel() {
            status = .disconnecting

            SimulatorTunnelProvider.shared.delegate.stopTunnel(with: .userInitiated) {
                self.status = .disconnected
                self.connectedDate = nil
            }
        }

        private func postStatusDidChangeNotification() {
            NotificationCenter.default.post(name: .NEVPNStatusDidChange, object: self)
        }
    }

#endif