summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2026-04-09 21:38:07 +0200
committerEmīls <emils@mullvad.net>2026-04-09 21:38:07 +0200
commitffe05e6dfcc78d619fb63de24f19a681118ffc6d (patch)
treeeb9075c989fe2878ac9f8367655c3bd70b0b5625
parent71727c995248d09f6f18f6b28091f3786f2c6902 (diff)
parentb124b0b9b71d62dcf25f25bf7e0d42932cfca39a (diff)
downloadmullvadvpn-ffe05e6dfcc78d619fb63de24f19a681118ffc6d.tar.xz
mullvadvpn-ffe05e6dfcc78d619fb63de24f19a681118ffc6d.zip
Merge branch 'add-skeleton-class-to-have-2-separate-implementations-of-the-ios-1534'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj24
-rw-r--r--ios/MullvadVPN/View controllers/Account/AccountViewController.swift14
-rw-r--r--ios/PacketTunnel/GotaTunAdapter/GotaTunActor.swift84
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift120
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorProtocol.swift27
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/PacketTunnelActorStub.swift25
-rw-r--r--ios/Shared/PacketTunnelDebugSettings.swift33
7 files changed, 280 insertions, 47 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index ce42041058..719e918ca2 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -7,6 +7,12 @@
objects = {
/* Begin PBXBuildFile section */
+ E10A0001000000000000AB01 /* GotaTunActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0001000000000000AA01 /* GotaTunActor.swift */; };
+ E10A0002000000000000AB01 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; };
+ E10A0002000000000000AB02 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; };
+ E10A0002000000000000AB03 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; };
+ E10A0002000000000000AB04 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; };
+ E10A0002000000000000AB05 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; };
0107F40B2F5B02580012451B /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 0107F40A2F5B02580012451B /* WireGuardKitTypes */; };
0107F40D2F5B02840012451B /* MullvadTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223D5294C8E5E0029F5F8 /* MullvadTypes.framework */; };
0107F4142F5B02D70012451B /* MullvadLogging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223F3294C8FF00029F5F8 /* MullvadLogging.framework */; };
@@ -1713,6 +1719,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ E10A0001000000000000AA01 /* GotaTunActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GotaTunActor.swift; sourceTree = "<group>"; };
+ E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelDebugSettings.swift; sourceTree = "<group>"; };
0107F3F82F56E3ED0012451B /* RelayCacheTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTrackerTests.swift; sourceTree = "<group>"; };
0107F4212F5B97F30012451B /* RelayListCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayListCacheTests.swift; sourceTree = "<group>"; };
014E8C2F2F294FB000837D0A /* relays-test-data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "relays-test-data.json"; sourceTree = "<group>"; };
@@ -3840,6 +3848,7 @@
58C76A072A33850E00100D75 /* ApplicationTarget.swift */,
F0EF2B762F4C801D00C7ECA7 /* AppResetManager.swift */,
F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */,
+ E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */,
F0AF894A2F55A77800DE9740 /* UITestSettingsResetPolicy.swift */,
);
path = Shared;
@@ -4137,11 +4146,20 @@
path = MullvadVPN;
sourceTree = "<group>";
};
+ E10A0001000000000000AC01 /* GotaTunAdapter */ = {
+ isa = PBXGroup;
+ children = (
+ E10A0001000000000000AA01 /* GotaTunActor.swift */,
+ );
+ path = GotaTunAdapter;
+ sourceTree = "<group>";
+ };
58CE5E7A224146470008646E /* PacketTunnel */ = {
isa = PBXGroup;
children = (
F09D616A2F5AD5E200C85C9E /* Localizable.xcstrings */,
58915D662A25F9F20066445B /* DeviceCheck */,
+ E10A0001000000000000AC01 /* GotaTunAdapter */,
7A9ED2A82F3CC057005BC0D9 /* Notifications */,
58F3F3682AA08E2200D3B0A4 /* PacketTunnelProvider */,
F059197A2C45404500C301F3 /* PostQuantum */,
@@ -6304,6 +6322,7 @@
A9C7B62C2EB9F71D002CABB1 /* LoggerBuilderTests.swift in Sources */,
A9A5FA352ACB05160083449F /* WgKeyRotationTests.swift in Sources */,
F0A7EBB62CF092CC005BB671 /* ApplicationConfiguration.swift in Sources */,
+ E10A0002000000000000AB01 /* PacketTunnelDebugSettings.swift in Sources */,
7AB4CCB92B69097E006037F5 /* IPOverrideTests.swift in Sources */,
A9A5FA362ACB05160083449F /* TunnelManagerTests.swift in Sources */,
);
@@ -6321,6 +6340,7 @@
F0E61CAA2BF2911D000C4A95 /* TunnelSettingsV5.swift in Sources */,
7A5869BD2B56EF7300640D27 /* IPOverride.swift in Sources */,
58B2FDEE2AA72098003EB5C6 /* ApplicationConfiguration.swift in Sources */,
+ E10A0002000000000000AB02 /* PacketTunnelDebugSettings.swift in Sources */,
F050AE572B7376C6003F4EDB /* CustomListRepositoryProtocol.swift in Sources */,
58B2FDE52AA71D5C003EB5C6 /* TunnelSettingsV2.swift in Sources */,
A97D30172AE6B5E90045C0E4 /* StoredWgKeyData.swift in Sources */,
@@ -6465,6 +6485,7 @@
586C0D852B03D31E00E7CDD7 /* SocksSectionHandler.swift in Sources */,
7A5468AC2C6A55B100590086 /* LocationRelays.swift in Sources */,
58BFA5CC22A7CE1F00A6173D /* ApplicationConfiguration.swift in Sources */,
+ E10A0002000000000000AB03 /* PacketTunnelDebugSettings.swift in Sources */,
5891BF5125E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift in Sources */,
58E511E628DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */,
58C76A0B2A338E4300100D75 /* BackgroundTask.swift in Sources */,
@@ -6942,6 +6963,8 @@
58C7A45B2A8640030060C66F /* PacketTunnelPathObserver.swift in Sources */,
580D6B8E2AB33BBF00B2D6E0 /* BlockedStateErrorMapper.swift in Sources */,
06AC116228F94C450037AF9A /* ApplicationConfiguration.swift in Sources */,
+ E10A0001000000000000AB01 /* GotaTunActor.swift in Sources */,
+ E10A0002000000000000AB04 /* PacketTunnelDebugSettings.swift in Sources */,
58CE38C728992C8700A6D6E5 /* WireGuardAdapterError+Localization.swift in Sources */,
58E511E828DDDF2400B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */,
58FDF2D92A0BA11A00C2B061 /* DeviceCheckOperation.swift in Sources */,
@@ -7033,6 +7056,7 @@
buildActionMask = 2147483647;
files = (
7AED35CC2BD13F60002A67D1 /* ApplicationConfiguration.swift in Sources */,
+ E10A0002000000000000AB05 /* PacketTunnelDebugSettings.swift in Sources */,
7AED35CD2BD13FC4002A67D1 /* ApplicationTarget.swift in Sources */,
58D22402294C90050029F5F8 /* Logger+Errors.swift in Sources */,
58D22404294C90050029F5F8 /* Date+LogFormat.swift in Sources */,
diff --git a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
index cc732d7d32..8040e064b8 100644
--- a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
+++ b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
@@ -277,6 +277,20 @@ class AccountViewController: UIViewController, @unchecked Sendable {
)
)
+ #if DEBUG
+ let gotaTunEnabled = PacketTunnelDebugSettings.useGotaTun
+ sheetController.addAction(
+ UIAlertAction(
+ title: "Use GotaTun: \(gotaTunEnabled ? "ON" : "OFF")",
+ style: .default,
+ handler: { [weak self] _ in
+ PacketTunnelDebugSettings.useGotaTun = !gotaTunEnabled
+ self?.interactor.tunnelManager.reapplyTunnelConfiguration()
+ }
+ )
+ )
+ #endif
+
sheetController.addAction(
UIAlertAction(
title: "Cancel",
diff --git a/ios/PacketTunnel/GotaTunAdapter/GotaTunActor.swift b/ios/PacketTunnel/GotaTunAdapter/GotaTunActor.swift
new file mode 100644
index 0000000000..7b90e62816
--- /dev/null
+++ b/ios/PacketTunnel/GotaTunAdapter/GotaTunActor.swift
@@ -0,0 +1,84 @@
+//
+// GotaTunActor.swift
+// PacketTunnel
+//
+// Created by Mullvad VPN.
+// Copyright © 2026 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadLogging
+import MullvadTypes
+import Network
+import PacketTunnelCore
+
+/// Stub actor for GotaTun tunnel implementation.
+/// Implements `PacketTunnelActorProtocol` with no-op methods — the real
+/// GotaTun logic will be filled in later.
+final class GotaTunActor: PacketTunnelActorProtocol, @unchecked Sendable {
+ private let logger = Logger(label: "GotaTunActor")
+
+ var observedState: ObservedState {
+ get async { .disconnected }
+ }
+
+ var observedStates: AsyncStream<ObservedState> {
+ get async {
+ AsyncStream { continuation in
+ continuation.yield(.disconnected)
+ continuation.finish()
+ }
+ }
+ }
+
+ init() {
+ logger.info("GotaTunActor initialized (stub)")
+ }
+
+ func start(options: StartOptions) {
+ logger.info("start called (no-op)")
+ }
+
+ func stop() {
+ logger.info("stop called (no-op)")
+ }
+
+ func waitUntilDisconnected() async {
+ logger.info("waitUntilDisconnected called (no-op)")
+ }
+
+ func onSleep() {
+ logger.info("onSleep called (no-op)")
+ }
+
+ func onWake() {
+ logger.info("onWake called (no-op)")
+ }
+
+ func updateNetworkReachability(networkPathStatus: NWPath.Status) {
+ logger.info("updateNetworkReachability called (no-op)")
+ }
+
+ func reconnect(to nextRelays: NextRelays, reconnectReason: ActorReconnectReason) {
+ logger.info("reconnect called (no-op)")
+ }
+
+ func notifyKeyRotation(date: Date?) {
+ logger.info("notifyKeyRotation called (no-op)")
+ }
+
+ func setErrorState(reason: BlockedStateReason) {
+ logger.info("setErrorState called (no-op)")
+ }
+
+ func notifyEphemeralPeerNegotiated() {
+ logger.info("notifyEphemeralPeerNegotiated called (no-op)")
+ }
+
+ func changeEphemeralPeerNegotiationState(
+ configuration: EphemeralPeerNegotiationState,
+ reconfigurationSemaphore: OneshotChannel
+ ) {
+ logger.info("changeEphemeralPeerNegotiationState called (no-op)")
+ }
+}
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index b49c3521ba..adda7fb7d9 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
@@ -20,7 +20,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
private let internalQueue = DispatchQueue(label: "PacketTunnel-internalQueue")
private let providerLogger: Logger
- private var actor: PacketTunnelActor!
+ private var actor: (any PacketTunnelActorProtocol)!
private var appMessageHandler: AppMessageHandler!
private var stateObserverTask: AnyTask?
private var deviceChecker: DeviceChecker!
@@ -90,17 +90,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
)
)
- adapter = WgAdapter(packetTunnelProvider: self)
-
- let pinger = TunnelPinger(pingProvider: adapter.icmpPingProvider, replyQueue: internalQueue)
-
- let tunnelMonitor = TunnelMonitor(
- eventQueue: internalQueue,
- pinger: pinger,
- tunnelDeviceInfo: adapter,
- timings: TunnelMonitorTimings()
- )
-
let proxyFactory = REST.ProxyFactory.makeProxyFactory(
apiTransportProvider: apiTransportProvider
)
@@ -108,23 +97,25 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
let devicesProxy = proxyFactory.createDevicesProxy()
deviceChecker = DeviceChecker(accountsProxy: accountsProxy, devicesProxy: devicesProxy)
- relaySelector = RelaySelectorWrapper(
- relayCache: ipOverrideWrapper
- )
- actor = PacketTunnelActor(
- timings: PacketTunnelActorTimings(),
- tunnelAdapter: adapter,
- tunnelMonitor: tunnelMonitor,
- defaultPathObserver: defaultPathObserver,
- blockedStateErrorMapper: BlockedStateErrorMapper(),
- relaySelector: relaySelector,
- settingsReader: settingsReader,
- protocolObfuscator: ProtocolObfuscator<TunnelObfuscator>()
- )
-
- // Since PacketTunnelActor depends on the path observer, start observing after actor has been initalized.
- startDefaultPathObserver()
+ #if DEBUG
+ if PacketTunnelDebugSettings.useGotaTun {
+ providerLogger.info("Using GotaTunActor (debug)")
+ actor = GotaTunActor()
+ } else {
+ setUpWireGuardActor(
+ ipOverrideWrapper: ipOverrideWrapper,
+ settingsReader: settingsReader,
+ apiTransportProvider: apiTransportProvider
+ )
+ }
+ #else
+ setUpWireGuardActor(
+ ipOverrideWrapper: ipOverrideWrapper,
+ settingsReader: settingsReader,
+ apiTransportProvider: apiTransportProvider
+ )
+ #endif
let apiRequestProxy = APIRequestProxy(
dispatchQueue: internalQueue,
@@ -135,25 +126,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
apiRequestProxy: apiRequestProxy
)
- ephemeralPeerExchangingPipeline = EphemeralPeerExchangingPipeline(
- EphemeralPeerExchangeActor(
- packetTunnel: ephemeralPeerReceiver,
- onFailure: self.ephemeralPeerExchangeFailed,
- iteratorProvider: { REST.RetryStrategy.postQuantumKeyExchange.makeDelayIterator() }
- ),
- onUpdateConfiguration: { [unowned self] configuration in
- let channel = OneshotChannel()
- actor.changeEphemeralPeerNegotiationState(
- configuration: configuration,
- reconfigurationSemaphore: channel
- )
- await channel.receive()
- },
- onFinish: { [unowned self] in
- actor.notifyEphemeralPeerNegotiated()
- }
- )
-
newAppVersionSystemNoticationHandler = NewAppVersionSystemNotificationHandler(
appVersionService: AppVersionService(
urlSession: URLSession.shared,
@@ -295,6 +267,60 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
)
}
+ private func setUpWireGuardActor(
+ ipOverrideWrapper: IPOverrideWrapper,
+ settingsReader: sending TunnelSettingsManager,
+ apiTransportProvider: APITransportProvider
+ ) {
+ adapter = WgAdapter(packetTunnelProvider: self)
+
+ let pinger = TunnelPinger(pingProvider: adapter.icmpPingProvider, replyQueue: internalQueue)
+
+ let tunnelMonitor = TunnelMonitor(
+ eventQueue: internalQueue,
+ pinger: pinger,
+ tunnelDeviceInfo: adapter,
+ timings: TunnelMonitorTimings()
+ )
+
+ relaySelector = RelaySelectorWrapper(
+ relayCache: ipOverrideWrapper
+ )
+
+ actor = PacketTunnelActor(
+ timings: PacketTunnelActorTimings(),
+ tunnelAdapter: adapter,
+ tunnelMonitor: tunnelMonitor,
+ defaultPathObserver: defaultPathObserver,
+ blockedStateErrorMapper: BlockedStateErrorMapper(),
+ relaySelector: relaySelector,
+ settingsReader: settingsReader,
+ protocolObfuscator: ProtocolObfuscator<TunnelObfuscator>()
+ )
+
+ // Since PacketTunnelActor depends on the path observer, start observing after actor has been initalized.
+ startDefaultPathObserver()
+
+ ephemeralPeerExchangingPipeline = EphemeralPeerExchangingPipeline(
+ EphemeralPeerExchangeActor(
+ packetTunnel: ephemeralPeerReceiver,
+ onFailure: self.ephemeralPeerExchangeFailed,
+ iteratorProvider: { REST.RetryStrategy.postQuantumKeyExchange.makeDelayIterator() }
+ ),
+ onUpdateConfiguration: { [unowned self] configuration in
+ let channel = OneshotChannel()
+ actor.changeEphemeralPeerNegotiationState(
+ configuration: configuration,
+ reconfigurationSemaphore: channel
+ )
+ await channel.receive()
+ },
+ onFinish: { [unowned self] in
+ actor.notifyEphemeralPeerNegotiated()
+ }
+ )
+ }
+
private func initialTunnelNetworkSettings() -> NETunnelNetworkSettings {
let settings = NEPacketTunnelNetworkSettings(
tunnelRemoteAddress: "\(IPv4Address.loopback)"
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorProtocol.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorProtocol.swift
index 934b1be621..449d8e238c 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActorProtocol.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorProtocol.swift
@@ -7,10 +7,37 @@
//
import Foundation
+import MullvadTypes
+import Network
public protocol PacketTunnelActorProtocol {
+ // State observation
var observedState: ObservedState { get async }
+ var observedStates: AsyncStream<ObservedState> { get async }
+ // Lifecycle
+ func start(options: StartOptions)
+ func stop()
+ func waitUntilDisconnected() async
+
+ // Sleep cycle
+ func onSleep()
+ func onWake()
+
+ // Network
+ func updateNetworkReachability(networkPathStatus: NWPath.Status)
+
+ // Reconnection & key rotation
func reconnect(to nextRelays: NextRelays, reconnectReason: ActorReconnectReason)
func notifyKeyRotation(date: Date?)
+
+ // Error state
+ func setErrorState(reason: BlockedStateReason)
+
+ // Ephemeral peer negotiation
+ func notifyEphemeralPeerNegotiated()
+ func changeEphemeralPeerNegotiationState(
+ configuration: EphemeralPeerNegotiationState,
+ reconfigurationSemaphore: OneshotChannel
+ )
}
diff --git a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActorStub.swift b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActorStub.swift
index 523acb6acc..2d83536912 100644
--- a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActorStub.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActorStub.swift
@@ -7,10 +7,35 @@
//
import Foundation
+import MullvadTypes
+import Network
import PacketTunnelCore
import XCTest
struct PacketTunnelActorStub: PacketTunnelActorProtocol {
+ var observedStates: AsyncStream<ObservedState> {
+ get async {
+ AsyncStream { continuation in
+ continuation.yield(innerState)
+ continuation.finish()
+ }
+ }
+ }
+
+ func start(options: StartOptions) {}
+ func stop() {}
+ func waitUntilDisconnected() async {}
+ func onSleep() {}
+ func onWake() {}
+ func updateNetworkReachability(networkPathStatus: NWPath.Status) {}
+ func setErrorState(reason: BlockedStateReason) {}
+ func notifyEphemeralPeerNegotiated() {}
+
+ func changeEphemeralPeerNegotiationState(
+ configuration: EphemeralPeerNegotiationState,
+ reconfigurationSemaphore: OneshotChannel
+ ) {}
+
let innerState: ObservedState = .disconnected
var stateExpectation: XCTestExpectation?
var reconnectExpectation: XCTestExpectation?
diff --git a/ios/Shared/PacketTunnelDebugSettings.swift b/ios/Shared/PacketTunnelDebugSettings.swift
new file mode 100644
index 0000000000..8aa1a7ba60
--- /dev/null
+++ b/ios/Shared/PacketTunnelDebugSettings.swift
@@ -0,0 +1,33 @@
+//
+// PacketTunnelDebugSettings.swift
+// MullvadVPN
+//
+// Created by Mullvad VPN.
+// Copyright © 2026 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+#if DEBUG
+ /// Debug settings for switching between packet tunnel implementations.
+ /// Stored in the shared App Group UserDefaults so both the main app and the
+ /// packet tunnel extension can access them.
+ enum PacketTunnelDebugSettings {
+ private static let useGotaTunKey = "PacketTunnelDebugSettings.useGotaTun"
+
+ private static var sharedDefaults: UserDefaults? {
+ UserDefaults(suiteName: ApplicationConfiguration.securityGroupIdentifier)
+ }
+
+ /// Whether the GotaTun adapter should be used instead of WireGuard.
+ /// Defaults to `false` if the shared container is unavailable.
+ static var useGotaTun: Bool {
+ get {
+ sharedDefaults?.bool(forKey: useGotaTunKey) ?? false
+ }
+ set {
+ sharedDefaults?.set(newValue, forKey: useGotaTunKey)
+ }
+ }
+ }
+#endif