summaryrefslogtreecommitdiffhomepage
path: root/ios/PacketTunnel/PacketTunnelProvider/DeviceChecker.swift
blob: dbce9f5fd16a11681e7b15527187c3f7b40398db (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
//
//  DeviceChecker.swift
//  PacketTunnel
//
//  Created by pronebird on 12/09/2023.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadREST
import MullvadTypes
import Operations
import PacketTunnelCore

final class DeviceChecker {
    private let dispatchQueue = DispatchQueue(label: "DeviceCheckerQueue")
    private let operationQueue = AsyncOperationQueue.makeSerial()

    private let accountsProxy: RESTAccountHandling
    private let devicesProxy: DeviceHandling

    init(accountsProxy: RESTAccountHandling, devicesProxy: DeviceHandling) {
        self.accountsProxy = accountsProxy
        self.devicesProxy = devicesProxy
    }

    /**
     Start device diagnostics to determine the reason why the tunnel is not functional.
    
     This involves the following steps:
    
     1. Fetch account and device data.
     2. Check account validity and whether it has enough time left.
     3. Verify that current device is registered with backend and that both device and backend point to the same public
        key.
     4. Rotate WireGuard key on key mismatch.
     */
    func start(rotateKeyOnMismatch: Bool) async -> Result<DeviceCheck, Error> {
        let checkOperation = DeviceCheckOperation(
            dispatchQueue: dispatchQueue,
            remoteSevice: DeviceCheckRemoteService(accountsProxy: accountsProxy, devicesProxy: devicesProxy),
            deviceStateAccessor: DeviceStateAccessor(),
            rotateImmediatelyOnKeyMismatch: rotateKeyOnMismatch
        )

        return await withTaskCancellationHandler {
            return await withCheckedContinuation { continuation in
                checkOperation.completionHandler = { result in
                    continuation.resume(with: .success(result))
                }
                operationQueue.addOperation(checkOperation)
            }
        } onCancel: {
            checkOperation.cancel()
        }
    }
}