-
Notifications
You must be signed in to change notification settings - Fork 392
/
Copy pathMapConnectionStatusOperation.swift
166 lines (142 loc) · 5.53 KB
/
MapConnectionStatusOperation.swift
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//
// MapConnectionStatusOperation.swift
// MullvadVPN
//
// Created by pronebird on 15/12/2021.
// Copyright © 2021 Mullvad VPN AB. All rights reserved.
//
import Foundation
import MullvadLogging
import MullvadREST
import MullvadTypes
import NetworkExtension
import Operations
import PacketTunnelCore
class MapConnectionStatusOperation: AsyncOperation {
private let interactor: TunnelInteractor
private let connectionStatus: NEVPNStatus
private var request: Cancellable?
private var pathStatus: Network.NWPath.Status?
private let logger = Logger(label: "TunnelManager.MapConnectionStatusOperation")
init(
queue: DispatchQueue,
interactor: TunnelInteractor,
connectionStatus: NEVPNStatus,
networkStatus: Network.NWPath.Status?
) {
self.interactor = interactor
self.connectionStatus = connectionStatus
pathStatus = networkStatus
super.init(dispatchQueue: queue)
}
override func main() {
guard let tunnel = interactor.tunnel else {
setTunnelDisconnectedStatus()
finish()
return
}
let tunnelState = interactor.tunnelStatus.state
switch connectionStatus {
case .connecting, .reasserting, .connected:
fetchTunnelStatus(tunnel: tunnel) { observedState in
switch observedState {
case let .connected(connectionState):
return connectionState.isNetworkReachable
? .connected(connectionState.selectedRelay)
: .waitingForConnectivity(.noConnection)
case let .connecting(connectionState):
return connectionState.isNetworkReachable
? .connecting(connectionState.selectedRelay)
: .waitingForConnectivity(.noConnection)
#if DEBUG
case let .negotiatingKey(connectionState):
return connectionState.isNetworkReachable
? .negotiatingKey(connectionState.selectedRelay)
: .waitingForConnectivity(.noConnection)
#endif
case let .reconnecting(connectionState):
return connectionState.isNetworkReachable
? .reconnecting(connectionState.selectedRelay)
: .waitingForConnectivity(.noConnection)
case let .error(blockedState):
return .error(blockedState.reason)
case .initial, .disconnecting, .disconnected:
return .none
}
}
return
case .disconnected:
handleDisconnectedState(tunnelState)
case .disconnecting:
handleDisconnectingState(tunnelState)
case .invalid:
setTunnelDisconnectedStatus()
@unknown default:
logger.debug("Unknown NEVPNStatus: \(connectionStatus.rawValue)")
}
finish()
}
override func operationDidCancel() {
request?.cancel()
}
private func handleDisconnectingState(_ tunnelState: TunnelState) {
switch tunnelState {
case .disconnecting:
break
default:
interactor.updateTunnelStatus { tunnelStatus in
// Avoid displaying waiting for connectivity banners if the tunnel in a blocked state when disconnecting
if tunnelStatus.observedState.blockedState != nil {
tunnelStatus.state = .disconnecting(.nothing)
} else {
let isNetworkReachable = tunnelStatus.observedState.connectionState?.isNetworkReachable ?? false
tunnelStatus.state = isNetworkReachable
? .disconnecting(.nothing)
: .waitingForConnectivity(.noNetwork)
}
}
}
}
private func handleDisconnectedState(_ tunnelState: TunnelState) {
switch tunnelState {
case .pendingReconnect:
logger.debug("Ignore disconnected state when pending reconnect.")
case .disconnecting(.reconnect):
logger.debug("Restart the tunnel on disconnect.")
interactor.updateTunnelStatus { tunnelStatus in
tunnelStatus = TunnelStatus()
tunnelStatus.state = .pendingReconnect
}
interactor.startTunnel()
default:
setTunnelDisconnectedStatus()
}
}
private func setTunnelDisconnectedStatus() {
interactor.updateTunnelStatus { tunnelStatus in
tunnelStatus = TunnelStatus()
tunnelStatus.state = pathStatus == .unsatisfied
? .waitingForConnectivity(.noNetwork)
: .disconnected
}
}
private func fetchTunnelStatus(
tunnel: any TunnelProtocol,
mapToState: @escaping (ObservedState) -> TunnelState?
) {
request = tunnel.getTunnelStatus { [weak self] result in
guard let self else { return }
dispatchQueue.async {
if case let .success(observedState) = result, !self.isCancelled {
self.interactor.updateTunnelStatus { tunnelStatus in
tunnelStatus.observedState = observedState
if let newState = mapToState(observedState) {
tunnelStatus.state = newState
}
}
}
self.finish()
}
}
}
}