summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift
blob: 08fcfdcd31efccd75dc31d0e61cb63b43f97e7e0 (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
//
//  LoadTunnelConfigurationOperation.swift
//  MullvadVPN
//
//  Created by pronebird on 16/12/2021.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadLogging
import MullvadSettings
import MullvadTypes
import Operations

class LoadTunnelConfigurationOperation: ResultOperation<Void>, @unchecked Sendable {
    private let logger = Logger(label: "LoadTunnelConfigurationOperation")
    private let interactor: TunnelInteractor

    init(dispatchQueue: DispatchQueue, interactor: TunnelInteractor) {
        self.interactor = interactor

        super.init(dispatchQueue: dispatchQueue)
    }

    override func main() {
        let settingsResult = readSettings()
        let deviceStateResult = readDeviceState()

        let persistentTunnels = interactor.getPersistentTunnels()
        let tunnel = persistentTunnels.first
        let settings = settingsResult.flattenValue()
        let deviceState = deviceStateResult.flattenValue()

        interactor.setSettings(settings ?? LatestTunnelSettings(), persist: false)
        interactor.setDeviceState(deviceState ?? .loggedOut, persist: false)

        if let tunnel, deviceState == nil {
            logger.debug("Remove orphaned VPN configuration.")

            tunnel.removeFromPreferences { error in
                if let error {
                    self.logger.error(
                        error: error,
                        message: "Failed to remove VPN configuration."
                    )
                }
                self.finishOperation(tunnel: nil)
            }
        } else {
            finishOperation(tunnel: tunnel)
        }
    }

    private func finishOperation(tunnel: (any TunnelProtocol)?) {
        interactor.setTunnel(tunnel, shouldRefreshTunnelState: true)
        interactor.setConfigurationLoaded()

        finish(result: .success(()))
    }

    private func readSettings() -> Result<LatestTunnelSettings?, Error> {
        Result { try SettingsManager.readSettings() }
            .flatMapError { error in
                if let error = error as? ReadSettingsVersionError,
                    let keychainError = error.underlyingError as? KeychainError, keychainError == .itemNotFound
                {
                    logger.debug("Settings not found in keychain.")

                    return .success(nil)
                } else {
                    logger.error(
                        error: error,
                        message: "Cannot read settings."
                    )

                    return .failure(error)
                }
            }
    }

    private func readDeviceState() -> Result<DeviceState?, Error> {
        Result { try SettingsManager.readDeviceState() }
            .flatMapError { error in
                if let error = error as? KeychainError, error == .itemNotFound {
                    logger.debug("Device state not found in keychain.")

                    return .success(nil)
                } else {
                    logger.error(
                        error: error,
                        message: "Cannot read device state."
                    )

                    return .failure(error)
                }
            }
    }
}