summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadREST/Transport/TransportStrategy.swift
blob: 00e3cef85c9b7cdb2da2094363893040ae850ffd (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
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
//
//  TransportStrategy.swift
//  MullvadREST
//
//  Created by Marco Nikic on 2023-04-27.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import Foundation
import Logging
@preconcurrency import MullvadRustRuntime
import MullvadSettings
import MullvadTypes

public struct TransportStrategy: Equatable, Sendable {
    /// The different transports suggested by the strategy
    public enum Transport: Equatable {
        /// Connecting a direct connection
        case direct

        /// Connecting via  shadowsocks proxy
        case shadowsocks(configuration: ShadowsocksConfiguration)

        /// Connecting via socks proxy
        case socks5(configuration: Socks5Configuration)

        /// Connecting via encrypted DNS proxy
        case encryptedDNS

        /// Failing  to retrieve transport
        case none

        public static func == (lhs: Self, rhs: Self) -> Bool {
            switch (lhs, rhs) {
            case (.direct, .direct), (.none, .none):
                true
            case let (.shadowsocks(lhsConfiguration), .shadowsocks(rhsConfiguration)):
                lhsConfiguration == rhsConfiguration
            case let (.socks5(lhsConfiguration), .socks5(rhsConfiguration)):
                lhsConfiguration == rhsConfiguration
            case (.encryptedDNS, .encryptedDNS):
                true
            default:
                false
            }
        }
    }

    private let shadowsocksLoader: ShadowsocksLoaderProtocol

    private let accessMethodIterator: AccessMethodIterator

    private let connectionModeProviderProxy: SwiftConnectionModeProviderProxy

    public let opaqueAccessMethodSettingsWrapper: SwiftAccessMethodSettingsWrapper

    public init(
        datasource: AccessMethodRepositoryDataSource,
        shadowsocksLoader: ShadowsocksLoaderProtocol
    ) {
        self.shadowsocksLoader = shadowsocksLoader
        self.accessMethodIterator = AccessMethodIterator(dataSource: datasource)
        self.connectionModeProviderProxy = SwiftConnectionModeProviderProxy(
            provider: accessMethodIterator,
            domainName: REST.encryptedDNSHostname
        )
        self
            .opaqueAccessMethodSettingsWrapper = initAccessMethodSettingsWrapper(
                methods:
                    connectionModeProviderProxy
                    .accessMethods()
            )
    }

    /// Rotating between enabled configurations by what order they were added in
    public func didFail() {
        let configuration = accessMethodIterator.pick()
        switch configuration.kind {
        case .bridges:
            try? shadowsocksLoader.clear()
            fallthrough
        default:
            self.accessMethodIterator.rotate()
        }
    }

    /// The suggested connection transport
    public func connectionTransport() -> Transport {
        let configuration = accessMethodIterator.pick()
        switch configuration.proxyConfiguration {
        case .direct:
            return .direct
        case .encryptedDNS:
            return .encryptedDNS
        case .bridges:
            do {
                let configuration = try shadowsocksLoader.load()
                return .shadowsocks(configuration: configuration)
            } catch {
                didFail()
                guard accessMethodIterator.pick().kind != .bridges else { return .none }
                return connectionTransport()
            }
        case let .shadowsocks(configuration):
            return .shadowsocks(
                configuration: ShadowsocksConfiguration(
                    address: configuration.server,
                    port: configuration.port,
                    password: configuration.password,
                    cipher: configuration.cipher.rawValue.description
                ))
        case let .socks5(configuration):
            return .socks5(
                configuration: Socks5Configuration(
                    proxyEndpoint: configuration.toAnyIPEndpoint,
                    username: configuration.credential?.username,
                    password: configuration.credential?.password
                ))
        }
    }

    public static func == (lhs: TransportStrategy, rhs: TransportStrategy) -> Bool {
        lhs.accessMethodIterator.pick() == rhs.accessMethodIterator.pick()
    }
}