summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadSettings/WireGuardObfuscationSettings.swift
blob: 3037978e58031923b93371d828d48cb5a74bae71 (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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//
//  WireGuardObfuscationSettings.swift
//  MullvadVPN
//
//  Created by Marco Nikic on 2023-10-17.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import Foundation

/// Whether obfuscation is enabled and which method is used.
///
/// `.automatic` means an algorithm will decide whether to use obfuscation or not.
public enum WireGuardObfuscationState: Codable, Sendable {
    @available(*, deprecated, renamed: "udpOverTcp")
    case on

    case automatic
    case udpOverTcp
    case shadowsocks
    case quic
    case off

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        var allKeys = ArraySlice(container.allKeys)
        guard let key = allKeys.popFirst(), allKeys.isEmpty else {
            throw DecodingError.typeMismatch(
                WireGuardObfuscationState.self,
                DecodingError.Context(
                    codingPath: container.codingPath,
                    debugDescription: "Invalid number of keys found, expected one.",
                    underlyingError: nil
                )
            )
        }

        switch key {
        case .automatic:
            self = .automatic
        case .on, .udpOverTcp:
            self = .udpOverTcp
        case .shadowsocks:
            self = .shadowsocks
        case .quic:
            self = .quic
        case .off:
            self = .off
        }
    }

    public var isEnabled: Bool {
        [.udpOverTcp, .shadowsocks, .quic].contains(self)
    }
}

public enum WireGuardObfuscationUdpOverTcpPort: Codable, Equatable, CustomStringConvertible, Sendable {
    case automatic
    case port80
    case port5001

    public var portValue: UInt16? {
        switch self {
        case .automatic:
            nil
        case .port80:
            80
        case .port5001:
            5001
        }
    }

    public var description: String {
        switch self {
        case .automatic:
            NSLocalizedString("Automatic", comment: "")
        case .port80:
            "80"
        case .port5001:
            "5001"
        }
    }
}

public enum WireGuardObfuscationShadowsocksPort: Codable, Equatable, CustomStringConvertible, Sendable {
    case automatic
    case custom(UInt16)

    public var portValue: UInt16? {
        switch self {
        case .automatic:
            nil
        case let .custom(port):
            port
        }
    }

    public var description: String {
        switch self {
        case .automatic:
            NSLocalizedString("Automatic", comment: "")
        case let .custom(port):
            String(port)
        }
    }
}

// Can't deprecate the whole type since it'll yield a lint warning when decoding
// port in `WireGuardObfuscationSettings`.
private enum WireGuardObfuscationPort: UInt16, Codable, Sendable {
    @available(*, deprecated, message: "Use `udpOverTcpPort` instead")
    case automatic = 0
    @available(*, deprecated, message: "Use `udpOverTcpPort` instead")
    case port80 = 80
    @available(*, deprecated, message: "Use `udpOverTcpPort` instead")
    case port5001 = 5001
}

public struct WireGuardObfuscationSettings: Codable, Equatable, Sendable {
    @available(*, deprecated, message: "Use `udpOverTcpPort` instead")
    private var port: WireGuardObfuscationPort = .automatic

    public var state: WireGuardObfuscationState
    public var udpOverTcpPort: WireGuardObfuscationUdpOverTcpPort
    public var shadowsocksPort: WireGuardObfuscationShadowsocksPort

    public init(
        state: WireGuardObfuscationState = .automatic,
        udpOverTcpPort: WireGuardObfuscationUdpOverTcpPort = .automatic,
        shadowsocksPort: WireGuardObfuscationShadowsocksPort = .automatic
    ) {
        self.state = state
        self.udpOverTcpPort = udpOverTcpPort
        self.shadowsocksPort = shadowsocksPort
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        state = try container.decode(WireGuardObfuscationState.self, forKey: .state)
        shadowsocksPort =
            try container.decodeIfPresent(
                WireGuardObfuscationShadowsocksPort.self,
                forKey: .shadowsocksPort
            ) ?? .automatic

        if let port = try? container.decodeIfPresent(WireGuardObfuscationUdpOverTcpPort.self, forKey: .udpOverTcpPort) {
            udpOverTcpPort = port
        } else if let port = try? container.decodeIfPresent(WireGuardObfuscationPort.self, forKey: .port) {
            switch port {
            case .automatic:
                udpOverTcpPort = .automatic
            case .port80:
                udpOverTcpPort = .port80
            case .port5001:
                udpOverTcpPort = .port5001
            }
        } else {
            udpOverTcpPort = .automatic
        }
    }
}