Merge pull request #40 from keeshux/report-tunnel-error-to-host-app
Report tunnel error to host app
This commit is contained in:
commit
d6526301b6
|
@ -74,13 +74,13 @@ extension TunnelKitProvider {
|
||||||
public static func deserialized(_ string: String) throws -> EndpointProtocol {
|
public static func deserialized(_ string: String) throws -> EndpointProtocol {
|
||||||
let components = string.components(separatedBy: ":")
|
let components = string.components(separatedBy: ":")
|
||||||
guard components.count == 2 else {
|
guard components.count == 2 else {
|
||||||
throw ProviderError.configuration(field: "endpointProtocol")
|
throw ProviderConfigurationError.parameter(name: "endpointProtocol")
|
||||||
}
|
}
|
||||||
guard let socketType = SocketType(rawValue: components[0]) else {
|
guard let socketType = SocketType(rawValue: components[0]) else {
|
||||||
throw ProviderError.configuration(field: "endpointProtocol.socketType")
|
throw ProviderConfigurationError.parameter(name: "endpointProtocol.socketType")
|
||||||
}
|
}
|
||||||
guard let port = UInt16(components[1]) else {
|
guard let port = UInt16(components[1]) else {
|
||||||
throw ProviderError.configuration(field: "endpointProtocol.port")
|
throw ProviderConfigurationError.parameter(name: "endpointProtocol.port")
|
||||||
}
|
}
|
||||||
return EndpointProtocol(socketType, port)
|
return EndpointProtocol(socketType, port)
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,9 @@ extension TunnelKitProvider {
|
||||||
/// Optional debug log format (SwiftyBeaver format).
|
/// Optional debug log format (SwiftyBeaver format).
|
||||||
public var debugLogFormat: String?
|
public var debugLogFormat: String?
|
||||||
|
|
||||||
|
/// The key in `defaults` where to set the raw value of last `TunnelKitProvider.ProviderError`.
|
||||||
|
public var lastErrorKey: String?
|
||||||
|
|
||||||
// MARK: Building
|
// MARK: Building
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,28 +191,29 @@ extension TunnelKitProvider {
|
||||||
shouldDebug = false
|
shouldDebug = false
|
||||||
debugLogKey = nil
|
debugLogKey = nil
|
||||||
debugLogFormat = nil
|
debugLogFormat = nil
|
||||||
|
lastErrorKey = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate init(providerConfiguration: [String: Any]) throws {
|
fileprivate init(providerConfiguration: [String: Any]) throws {
|
||||||
let S = Configuration.Keys.self
|
let S = Configuration.Keys.self
|
||||||
|
|
||||||
guard let cipherAlgorithm = providerConfiguration[S.cipherAlgorithm] as? String, let cipher = SessionProxy.Cipher(rawValue: cipherAlgorithm) else {
|
guard let cipherAlgorithm = providerConfiguration[S.cipherAlgorithm] as? String, let cipher = SessionProxy.Cipher(rawValue: cipherAlgorithm) else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.cipherAlgorithm)]")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.cipherAlgorithm)]")
|
||||||
}
|
}
|
||||||
guard let digestAlgorithm = providerConfiguration[S.digestAlgorithm] as? String, let digest = SessionProxy.Digest(rawValue: digestAlgorithm) else {
|
guard let digestAlgorithm = providerConfiguration[S.digestAlgorithm] as? String, let digest = SessionProxy.Digest(rawValue: digestAlgorithm) else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.digestAlgorithm)]")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.digestAlgorithm)]")
|
||||||
}
|
}
|
||||||
|
|
||||||
let ca: CryptoContainer
|
let ca: CryptoContainer
|
||||||
let clientCertificate: CryptoContainer?
|
let clientCertificate: CryptoContainer?
|
||||||
let clientKey: CryptoContainer?
|
let clientKey: CryptoContainer?
|
||||||
guard let caPEM = providerConfiguration[S.ca] as? String else {
|
guard let caPEM = providerConfiguration[S.ca] as? String else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.ca)]")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.ca)]")
|
||||||
}
|
}
|
||||||
ca = CryptoContainer(pem: caPEM)
|
ca = CryptoContainer(pem: caPEM)
|
||||||
if let clientPEM = providerConfiguration[S.clientCertificate] as? String {
|
if let clientPEM = providerConfiguration[S.clientCertificate] as? String {
|
||||||
guard let keyPEM = providerConfiguration[S.clientKey] as? String else {
|
guard let keyPEM = providerConfiguration[S.clientKey] as? String else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.clientKey)]")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.clientKey)]")
|
||||||
}
|
}
|
||||||
|
|
||||||
clientCertificate = CryptoContainer(pem: clientPEM)
|
clientCertificate = CryptoContainer(pem: clientPEM)
|
||||||
|
@ -223,7 +227,7 @@ extension TunnelKitProvider {
|
||||||
resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String]
|
resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String]
|
||||||
|
|
||||||
guard let endpointProtocolsStrings = providerConfiguration[S.endpointProtocols] as? [String], !endpointProtocolsStrings.isEmpty else {
|
guard let endpointProtocolsStrings = providerConfiguration[S.endpointProtocols] as? [String], !endpointProtocolsStrings.isEmpty else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] is nil or empty")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] is nil or empty")
|
||||||
}
|
}
|
||||||
endpointProtocols = try endpointProtocolsStrings.map { try EndpointProtocol.deserialized($0) }
|
endpointProtocols = try endpointProtocolsStrings.map { try EndpointProtocol.deserialized($0) }
|
||||||
|
|
||||||
|
@ -242,7 +246,7 @@ extension TunnelKitProvider {
|
||||||
do {
|
do {
|
||||||
tlsWrap = try SessionProxy.TLSWrap.deserialized(tlsWrapData)
|
tlsWrap = try SessionProxy.TLSWrap.deserialized(tlsWrapData)
|
||||||
} catch {
|
} catch {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.tlsWrap)]")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.tlsWrap)]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keepAliveSeconds = providerConfiguration[S.keepAlive] as? Int
|
keepAliveSeconds = providerConfiguration[S.keepAlive] as? Int
|
||||||
|
@ -252,16 +256,17 @@ extension TunnelKitProvider {
|
||||||
shouldDebug = providerConfiguration[S.debug] as? Bool ?? false
|
shouldDebug = providerConfiguration[S.debug] as? Bool ?? false
|
||||||
if shouldDebug {
|
if shouldDebug {
|
||||||
guard let debugLogKey = providerConfiguration[S.debugLogKey] as? String else {
|
guard let debugLogKey = providerConfiguration[S.debugLogKey] as? String else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.debugLogKey)]")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.debugLogKey)]")
|
||||||
}
|
}
|
||||||
self.debugLogKey = debugLogKey
|
self.debugLogKey = debugLogKey
|
||||||
debugLogFormat = providerConfiguration[S.debugLogFormat] as? String
|
debugLogFormat = providerConfiguration[S.debugLogFormat] as? String
|
||||||
} else {
|
} else {
|
||||||
debugLogKey = nil
|
debugLogKey = nil
|
||||||
}
|
}
|
||||||
|
lastErrorKey = providerConfiguration[S.lastErrorKey] as? String
|
||||||
|
|
||||||
guard !prefersResolvedAddresses || !(resolvedAddresses?.isEmpty ?? true) else {
|
guard !prefersResolvedAddresses || !(resolvedAddresses?.isEmpty ?? true) else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.prefersResolvedAddresses)] is true but no [\(S.resolvedAddresses)]")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.prefersResolvedAddresses)] is true but no [\(S.resolvedAddresses)]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +293,8 @@ extension TunnelKitProvider {
|
||||||
usesPIAPatches: usesPIAPatches,
|
usesPIAPatches: usesPIAPatches,
|
||||||
shouldDebug: shouldDebug,
|
shouldDebug: shouldDebug,
|
||||||
debugLogKey: shouldDebug ? debugLogKey : nil,
|
debugLogKey: shouldDebug ? debugLogKey : nil,
|
||||||
debugLogFormat: shouldDebug ? debugLogFormat : nil
|
debugLogFormat: shouldDebug ? debugLogFormat : nil,
|
||||||
|
lastErrorKey: lastErrorKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,6 +337,8 @@ extension TunnelKitProvider {
|
||||||
static let debugLogKey = "DebugLogKey"
|
static let debugLogKey = "DebugLogKey"
|
||||||
|
|
||||||
static let debugLogFormat = "DebugLogFormat"
|
static let debugLogFormat = "DebugLogFormat"
|
||||||
|
|
||||||
|
static let lastErrorKey = "LastErrorKey"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.prefersResolvedAddresses`
|
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.prefersResolvedAddresses`
|
||||||
|
@ -384,6 +392,9 @@ extension TunnelKitProvider {
|
||||||
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.debugLogFormat`
|
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.debugLogFormat`
|
||||||
public let debugLogFormat: String?
|
public let debugLogFormat: String?
|
||||||
|
|
||||||
|
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.lastErrorKey`
|
||||||
|
public let lastErrorKey: String?
|
||||||
|
|
||||||
// MARK: Shortcuts
|
// MARK: Shortcuts
|
||||||
|
|
||||||
func existingLog(in defaults: UserDefaults) -> [String]? {
|
func existingLog(in defaults: UserDefaults) -> [String]? {
|
||||||
|
@ -404,7 +415,7 @@ extension TunnelKitProvider {
|
||||||
*/
|
*/
|
||||||
public static func appGroup(from providerConfiguration: [String: Any]) throws -> String {
|
public static func appGroup(from providerConfiguration: [String: Any]) throws -> String {
|
||||||
guard let appGroup = providerConfiguration[Keys.appGroup] as? String else {
|
guard let appGroup = providerConfiguration[Keys.appGroup] as? String else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(Keys.appGroup)]")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(Keys.appGroup)]")
|
||||||
}
|
}
|
||||||
return appGroup
|
return appGroup
|
||||||
}
|
}
|
||||||
|
@ -468,6 +479,9 @@ extension TunnelKitProvider {
|
||||||
if let debugLogFormat = debugLogFormat {
|
if let debugLogFormat = debugLogFormat {
|
||||||
dict[S.debugLogFormat] = debugLogFormat
|
dict[S.debugLogFormat] = debugLogFormat
|
||||||
}
|
}
|
||||||
|
if let lastErrorKey = lastErrorKey {
|
||||||
|
dict[S.lastErrorKey] = lastErrorKey
|
||||||
|
}
|
||||||
return dict
|
return dict
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,7 +505,7 @@ extension TunnelKitProvider {
|
||||||
do {
|
do {
|
||||||
try keychain.set(password: password, for: username, label: Bundle.main.bundleIdentifier)
|
try keychain.set(password: password, for: username, label: Bundle.main.bundleIdentifier)
|
||||||
} catch _ {
|
} catch _ {
|
||||||
throw ProviderError.credentials(field: "keychain.set()")
|
throw ProviderConfigurationError.credentials(details: "keychain.set()")
|
||||||
}
|
}
|
||||||
protocolConfiguration.username = username
|
protocolConfiguration.username = username
|
||||||
protocolConfiguration.passwordReference = try? keychain.passwordReference(for: username)
|
protocolConfiguration.passwordReference = try? keychain.passwordReference(for: username)
|
||||||
|
@ -562,6 +576,7 @@ extension TunnelKitProvider.Configuration: Equatable {
|
||||||
builder.shouldDebug = shouldDebug
|
builder.shouldDebug = shouldDebug
|
||||||
builder.debugLogKey = debugLogKey
|
builder.debugLogKey = debugLogKey
|
||||||
builder.debugLogFormat = debugLogFormat
|
builder.debugLogFormat = debugLogFormat
|
||||||
|
builder.lastErrorKey = lastErrorKey
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,20 +71,24 @@ extension TunnelKitProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The errors raised by `TunnelKitProvider`.
|
// mostly programming errors by host app
|
||||||
public enum ProviderError: Error {
|
enum ProviderConfigurationError: Error {
|
||||||
|
|
||||||
/// The `TunnelKitProvider.Configuration` provided is incorrect or incomplete.
|
/// A field in the `TunnelKitProvider.Configuration` provided is incorrect or incomplete.
|
||||||
case configuration(field: String)
|
case parameter(name: String)
|
||||||
|
|
||||||
/// Credentials are missing or protected (e.g. device locked).
|
/// Credentials are missing or inaccessible.
|
||||||
case credentials(field: String)
|
case credentials(details: String)
|
||||||
|
|
||||||
/// The pseudo-random number generator could not be initialized.
|
/// The pseudo-random number generator could not be initialized.
|
||||||
case prngInitialization
|
case prngInitialization
|
||||||
|
|
||||||
/// The TLS certificate could not be serialized.
|
/// The TLS certificate could not be serialized.
|
||||||
case certificateSerialization
|
case certificateSerialization
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The errors causing a tunnel disconnection.
|
||||||
|
public enum ProviderError: String, Error {
|
||||||
|
|
||||||
/// Socket endpoint could not be resolved.
|
/// Socket endpoint could not be resolved.
|
||||||
case dnsFailure
|
case dnsFailure
|
||||||
|
@ -95,10 +99,22 @@ extension TunnelKitProvider {
|
||||||
/// Socket failed to reach active state.
|
/// Socket failed to reach active state.
|
||||||
case socketActivity
|
case socketActivity
|
||||||
|
|
||||||
|
/// Credentials authentication failed.
|
||||||
|
case authenticationFailed
|
||||||
|
|
||||||
|
/// TLS handshake failed.
|
||||||
|
case tlsFailed
|
||||||
|
|
||||||
|
/// Tunnel timed out.
|
||||||
|
case timeout
|
||||||
|
|
||||||
/// An error occurred at the link level.
|
/// An error occurred at the link level.
|
||||||
case linkError
|
case linkError
|
||||||
|
|
||||||
/// The current network changed (e.g. switched from WiFi to data connection).
|
/// The current network changed (e.g. switched from WiFi to data connection).
|
||||||
case networkChanged
|
case networkChanged
|
||||||
|
|
||||||
|
/// The server replied in an unexpected way.
|
||||||
|
case unexpectedReply
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,23 +121,23 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
||||||
let hostname: String
|
let hostname: String
|
||||||
do {
|
do {
|
||||||
guard let tunnelProtocol = protocolConfiguration as? NETunnelProviderProtocol else {
|
guard let tunnelProtocol = protocolConfiguration as? NETunnelProviderProtocol else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration")
|
||||||
}
|
}
|
||||||
guard let serverAddress = tunnelProtocol.serverAddress else {
|
guard let serverAddress = tunnelProtocol.serverAddress else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.serverAddress")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.serverAddress")
|
||||||
}
|
}
|
||||||
guard let providerConfiguration = tunnelProtocol.providerConfiguration else {
|
guard let providerConfiguration = tunnelProtocol.providerConfiguration else {
|
||||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration")
|
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration")
|
||||||
}
|
}
|
||||||
hostname = serverAddress
|
hostname = serverAddress
|
||||||
try appGroup = Configuration.appGroup(from: providerConfiguration)
|
try appGroup = Configuration.appGroup(from: providerConfiguration)
|
||||||
try cfg = Configuration.parsed(from: providerConfiguration)
|
try cfg = Configuration.parsed(from: providerConfiguration)
|
||||||
} catch let e {
|
} catch let e {
|
||||||
var message: String?
|
var message: String?
|
||||||
if let te = e as? ProviderError {
|
if let te = e as? ProviderConfigurationError {
|
||||||
switch te {
|
switch te {
|
||||||
case .configuration(let field):
|
case .parameter(let name):
|
||||||
message = "Tunnel configuration incomplete: \(field)"
|
message = "Tunnel configuration incomplete: \(name)"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
@ -176,9 +176,10 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
||||||
)
|
)
|
||||||
|
|
||||||
log.info("Starting tunnel...")
|
log.info("Starting tunnel...")
|
||||||
|
clearErrorStatus()
|
||||||
|
|
||||||
guard SessionProxy.EncryptionBridge.prepareRandomNumberGenerator(seedLength: prngSeedLength) else {
|
guard SessionProxy.EncryptionBridge.prepareRandomNumberGenerator(seedLength: prngSeedLength) else {
|
||||||
completionHandler(ProviderError.prngInitialization)
|
completionHandler(ProviderConfigurationError.prngInitialization)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +191,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
||||||
try cfg.ca.write(to: url)
|
try cfg.ca.write(to: url)
|
||||||
caPath = url.path
|
caPath = url.path
|
||||||
} catch {
|
} catch {
|
||||||
completionHandler(ProviderError.certificateSerialization)
|
completionHandler(ProviderConfigurationError.certificateSerialization)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let clientCertificate = cfg.clientCertificate {
|
if let clientCertificate = cfg.clientCertificate {
|
||||||
|
@ -199,7 +200,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
||||||
try clientCertificate.write(to: url)
|
try clientCertificate.write(to: url)
|
||||||
clientCertificatePath = url.path
|
clientCertificatePath = url.path
|
||||||
} catch {
|
} catch {
|
||||||
completionHandler(ProviderError.certificateSerialization)
|
completionHandler(ProviderConfigurationError.certificateSerialization)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -211,7 +212,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
||||||
try clientKey.write(to: url)
|
try clientKey.write(to: url)
|
||||||
clientKeyPath = url.path
|
clientKeyPath = url.path
|
||||||
} catch {
|
} catch {
|
||||||
completionHandler(ProviderError.certificateSerialization)
|
completionHandler(ProviderConfigurationError.certificateSerialization)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -259,6 +260,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
||||||
open override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
open override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
pendingStartHandler = nil
|
pendingStartHandler = nil
|
||||||
log.info("Stopping tunnel...")
|
log.info("Stopping tunnel...")
|
||||||
|
clearErrorStatus()
|
||||||
|
|
||||||
guard let proxy = proxy else {
|
guard let proxy = proxy else {
|
||||||
flushLog()
|
flushLog()
|
||||||
|
@ -324,6 +326,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
private func connectTunnel(via socket: GenericSocket) {
|
private func connectTunnel(via socket: GenericSocket) {
|
||||||
log.info("Will connect to \(socket)")
|
log.info("Will connect to \(socket)")
|
||||||
|
clearErrorStatus()
|
||||||
|
|
||||||
log.debug("Socket type is \(type(of: socket))")
|
log.debug("Socket type is \(type(of: socket))")
|
||||||
self.socket = socket
|
self.socket = socket
|
||||||
|
@ -342,6 +345,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
log.error("Tunnel did stop (error: \(error))")
|
log.error("Tunnel did stop (error: \(error))")
|
||||||
|
setErrorStatus(with: error)
|
||||||
} else {
|
} else {
|
||||||
log.info("Tunnel did stop on request")
|
log.info("Tunnel did stop on request")
|
||||||
}
|
}
|
||||||
|
@ -599,6 +603,38 @@ extension TunnelKitProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setErrorStatus(with error: Error) {
|
||||||
|
guard let lastErrorKey = cfg.lastErrorKey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let providerError: ProviderError
|
||||||
|
if let se = error as? SessionError {
|
||||||
|
switch se {
|
||||||
|
case .badCredentials:
|
||||||
|
providerError = .authenticationFailed
|
||||||
|
|
||||||
|
case .peerVerification, .tlsError:
|
||||||
|
providerError = .tlsFailed
|
||||||
|
|
||||||
|
case .negotiationTimeout, .pingTimeout:
|
||||||
|
providerError = .timeout
|
||||||
|
|
||||||
|
default:
|
||||||
|
providerError = .unexpectedReply
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
providerError = error as? ProviderError ?? .linkError
|
||||||
|
}
|
||||||
|
defaults?.set(providerError.rawValue, forKey: lastErrorKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func clearErrorStatus() {
|
||||||
|
guard let lastErrorKey = cfg.lastErrorKey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defaults?.removeObject(forKey: lastErrorKey)
|
||||||
|
}
|
||||||
|
|
||||||
private func logCurrentSSID() {
|
private func logCurrentSSID() {
|
||||||
if let ssid = observer.currentWifiNetworkName() {
|
if let ssid = observer.currentWifiNetworkName() {
|
||||||
log.debug("Current SSID: '\(ssid)'")
|
log.debug("Current SSID: '\(ssid)'")
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// The possible errors raised/thrown during `SessionProxy` operation.
|
/// The possible errors raised/thrown during `SessionProxy` operation.
|
||||||
public enum SessionError: Error {
|
public enum SessionError: String, Error {
|
||||||
|
|
||||||
/// The negotiation timed out.
|
/// The negotiation timed out.
|
||||||
case negotiationTimeout
|
case negotiationTimeout
|
||||||
|
|
Loading…
Reference in New Issue