summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/AccessMethodRepository
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-11-02 10:47:15 +0100
committerJon Petersson <jon.petersson@kvadrat.se>2023-12-13 15:42:20 +0100
commit8188c51e1b0a5705db7e94053c6d8d45faf615fe (patch)
tree79b38d2b0ff8d8d2fd5b7ab06adde476c3c523e5 /ios/MullvadVPN/AccessMethodRepository
parentc1e2a227931e8e6011353ff9fce56fe733e4ac63 (diff)
downloadmullvadvpn-8188c51e1b0a5705db7e94053c6d8d45faf615fe.tar.xz
mullvadvpn-8188c51e1b0a5705db7e94053c6d8d45faf615fe.zip
Add API access methods UI/part of backend
Diffstat (limited to 'ios/MullvadVPN/AccessMethodRepository')
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/AccessMethodRepository.swift69
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/AccessMethodRepositoryProtocol.swift36
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/PersistentAccessMethod.swift124
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift37
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift21
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/ShadowsocksCipher.swift48
6 files changed, 335 insertions, 0 deletions
diff --git a/ios/MullvadVPN/AccessMethodRepository/AccessMethodRepository.swift b/ios/MullvadVPN/AccessMethodRepository/AccessMethodRepository.swift
new file mode 100644
index 0000000000..c0c0ec8607
--- /dev/null
+++ b/ios/MullvadVPN/AccessMethodRepository/AccessMethodRepository.swift
@@ -0,0 +1,69 @@
+//
+// AccessMethodRepository.swift
+// MullvadVPN
+//
+// Created by pronebird on 22/11/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Combine
+import Foundation
+
+class AccessMethodRepository: AccessMethodRepositoryProtocol {
+ private var memoryStore: [PersistentAccessMethod] {
+ didSet {
+ publisher.send(memoryStore)
+ }
+ }
+
+ let publisher: PassthroughSubject<[PersistentAccessMethod], Never> = .init()
+
+ static let shared = AccessMethodRepository()
+
+ init() {
+ memoryStore = [
+ PersistentAccessMethod(
+ id: UUID(uuidString: "C9DB7457-2A55-42C3-A926-C07F82131994")!,
+ name: "",
+ isEnabled: true,
+ proxyConfiguration: .direct
+ ),
+ PersistentAccessMethod(
+ id: UUID(uuidString: "8586E75A-CA7B-4432-B70D-EE65F3F95084")!,
+ name: "",
+ isEnabled: true,
+ proxyConfiguration: .bridges
+ ),
+ ]
+ }
+
+ func add(_ method: PersistentAccessMethod) {
+ guard !memoryStore.contains(where: { $0.id == method.id }) else { return }
+
+ memoryStore.append(method)
+ }
+
+ func update(_ method: PersistentAccessMethod) {
+ guard let index = memoryStore.firstIndex(where: { $0.id == method.id }) else { return }
+
+ memoryStore[index] = method
+ }
+
+ func delete(id: UUID) {
+ guard let index = memoryStore.firstIndex(where: { $0.id == id }) else { return }
+
+ // Prevent removing methods that have static UUIDs and always present.
+ let permanentMethod = memoryStore[index]
+ if !permanentMethod.kind.isPermanent {
+ memoryStore.remove(at: index)
+ }
+ }
+
+ func fetch(by id: UUID) -> PersistentAccessMethod? {
+ memoryStore.first { $0.id == id }
+ }
+
+ func fetchAll() -> [PersistentAccessMethod] {
+ memoryStore
+ }
+}
diff --git a/ios/MullvadVPN/AccessMethodRepository/AccessMethodRepositoryProtocol.swift b/ios/MullvadVPN/AccessMethodRepository/AccessMethodRepositoryProtocol.swift
new file mode 100644
index 0000000000..213f524bcc
--- /dev/null
+++ b/ios/MullvadVPN/AccessMethodRepository/AccessMethodRepositoryProtocol.swift
@@ -0,0 +1,36 @@
+//
+// AccessMethodRepositoryProtocol.swift
+// MullvadVPN
+//
+// Created by pronebird on 28/11/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Combine
+import Foundation
+
+protocol AccessMethodRepositoryProtocol {
+ /// Publisher that propagates a snapshot of persistent store upon modifications.
+ var publisher: PassthroughSubject<[PersistentAccessMethod], Never> { get }
+
+ /// Add new access method.
+ /// - Parameter method: persistent access method model.
+ func add(_ method: PersistentAccessMethod)
+
+ /// Persist modified access method locating existing entry by id.
+ /// - Parameter method: persistent access method model.
+ func update(_ method: PersistentAccessMethod)
+
+ /// Delete access method by id.
+ /// - Parameter id: an access method id.
+ func delete(id: UUID)
+
+ /// Fetch access method by id.
+ /// - Parameter id: an access method id.
+ /// - Returns: a persistent access method model upon success, otherwise `nil`.
+ func fetch(by id: UUID) -> PersistentAccessMethod?
+
+ /// Fetch all access method from the persistent store.
+ /// - Returns: an array of all persistent access method.
+ func fetchAll() -> [PersistentAccessMethod]
+}
diff --git a/ios/MullvadVPN/AccessMethodRepository/PersistentAccessMethod.swift b/ios/MullvadVPN/AccessMethodRepository/PersistentAccessMethod.swift
new file mode 100644
index 0000000000..b5d5ef2947
--- /dev/null
+++ b/ios/MullvadVPN/AccessMethodRepository/PersistentAccessMethod.swift
@@ -0,0 +1,124 @@
+//
+// PersistentAccessMethod.swift
+// MullvadVPN
+//
+// Created by pronebird on 15/11/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadTypes
+import Network
+
+/// Persistent access method model.
+struct PersistentAccessMethod: Identifiable, Codable {
+ /// The unique identifier used for referencing the access method entry in a persistent store.
+ var id: UUID
+
+ /// The user-defined name for access method.
+ var name: String
+
+ /// The flag indicating whether configuration is enabled.
+ var isEnabled: Bool
+
+ /// Proxy configuration.
+ var proxyConfiguration: PersistentProxyConfiguration
+}
+
+/// Persistent proxy configuration.
+enum PersistentProxyConfiguration: Codable {
+ /// Direct communication without proxy.
+ case direct
+
+ /// Communication over bridges.
+ case bridges
+
+ /// Communication over shadowsocks.
+ case shadowsocks(ShadowsocksConfiguration)
+
+ /// Communication over socks5 proxy.
+ case socks5(SocksConfiguration)
+}
+
+extension PersistentProxyConfiguration {
+ /// Socks autentication method.
+ enum SocksAuthentication: Codable {
+ case noAuthentication
+ case usernamePassword(username: String, password: String)
+ }
+
+ /// Socks v5 proxy configuration.
+ struct SocksConfiguration: Codable {
+ /// Proxy server address.
+ var server: AnyIPAddress
+
+ /// Proxy server port.
+ var port: UInt16
+
+ /// Authentication method.
+ var authentication: SocksAuthentication
+ }
+
+ /// Shadowsocks configuration.
+ struct ShadowsocksConfiguration: Codable {
+ /// Server address.
+ var server: AnyIPAddress
+
+ /// Server port.
+ var port: UInt16
+
+ /// Server password.
+ var password: String
+
+ /// Server cipher.
+ var cipher: ShadowsocksCipher
+ }
+}
+
+extension PersistentAccessMethod {
+ /// A kind of access method.
+ var kind: AccessMethodKind {
+ switch proxyConfiguration {
+ case .direct:
+ .direct
+ case .bridges:
+ .bridges
+ case .shadowsocks:
+ .shadowsocks
+ case .socks5:
+ .socks5
+ }
+ }
+}
+
+/// A kind of API access method.
+enum AccessMethodKind: Equatable, Hashable, CaseIterable {
+ /// Direct communication.
+ case direct
+
+ /// Communication over bridges.
+ case bridges
+
+ /// Communication over shadowsocks.
+ case shadowsocks
+
+ /// Communication over socks v5 proxy.
+ case socks5
+}
+
+extension AccessMethodKind {
+ /// Returns `true` if the method is permanent and cannot be deleted.
+ var isPermanent: Bool {
+ switch self {
+ case .direct, .bridges:
+ true
+ case .shadowsocks, .socks5:
+ false
+ }
+ }
+
+ /// Returns all access method kinds that can be added by user.
+ static var allUserDefinedKinds: [AccessMethodKind] {
+ allCases.filter { !$0.isPermanent }
+ }
+}
diff --git a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift
new file mode 100644
index 0000000000..bf9ad5f03a
--- /dev/null
+++ b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTester.swift
@@ -0,0 +1,37 @@
+//
+// ProxyConfigurationTester.swift
+// MullvadVPN
+//
+// Created by pronebird on 28/11/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Combine
+import Foundation
+
+/// A concrete implementation of an access method proxy configuration.
+class ProxyConfigurationTester: ProxyConfigurationTesterProtocol {
+ private var cancellable: Cancellable?
+
+ static let shared = ProxyConfigurationTester()
+
+ init() {}
+
+ func start(configuration: PersistentProxyConfiguration, completion: @escaping (Error?) -> Void) {
+ let workItem = DispatchWorkItem {
+ let randomResult = (0 ... 255).randomElement()?.isMultiple(of: 2) ?? true
+
+ completion(randomResult ? nil : URLError(.timedOut))
+ }
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: workItem)
+
+ cancellable = AnyCancellable {
+ workItem.cancel()
+ }
+ }
+
+ func cancel() {
+ cancellable = nil
+ }
+}
diff --git a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
new file mode 100644
index 0000000000..3ee795cbf9
--- /dev/null
+++ b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
@@ -0,0 +1,21 @@
+//
+// ProxyConfigurationTesterProtocol.swift
+// MullvadVPN
+//
+// Created by pronebird on 28/11/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Type implementing access method proxy configuration testing.
+protocol ProxyConfigurationTesterProtocol {
+ /// Start testing proxy configuration.
+ /// - Parameters:
+ /// - configuration: a proxy configuration.
+ /// - completion: a completion handler that receives `nil` upon success, otherwise the underlying error.
+ func start(configuration: PersistentProxyConfiguration, completion: @escaping (Error?) -> Void)
+
+ /// Cancel testing proxy configuration.
+ func cancel()
+}
diff --git a/ios/MullvadVPN/AccessMethodRepository/ShadowsocksCipher.swift b/ios/MullvadVPN/AccessMethodRepository/ShadowsocksCipher.swift
new file mode 100644
index 0000000000..8610bf33c1
--- /dev/null
+++ b/ios/MullvadVPN/AccessMethodRepository/ShadowsocksCipher.swift
@@ -0,0 +1,48 @@
+//
+// ShadowsocksCipher.swift
+// MullvadVPN
+//
+// Created by pronebird on 13/11/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Type representing a shadowsocks cipher.
+struct ShadowsocksCipher: RawRepresentable, CustomStringConvertible, Equatable, Hashable, Codable {
+ let rawValue: String
+
+ var description: String {
+ rawValue
+ }
+
+ /// Default cipher.
+ static let `default` = ShadowsocksCipher(rawValue: "chacha20")
+
+ /// All supported ciphers.
+ static let supportedCiphers = supportedCipherIdentifiers.map { ShadowsocksCipher(rawValue: $0) }
+}
+
+private let supportedCipherIdentifiers = [
+ // Stream ciphers.
+ "aes-128-cfb",
+ "aes-128-cfb1",
+ "aes-128-cfb8",
+ "aes-128-cfb128",
+ "aes-256-cfb",
+ "aes-256-cfb1",
+ "aes-256-cfb8",
+ "aes-256-cfb128",
+ "rc4",
+ "rc4-md5",
+ "chacha20",
+ "salsa20",
+ "chacha20-ietf",
+ // AEAD ciphers.
+ "aes-128-gcm",
+ "aes-256-gcm",
+ "chacha20-ietf-poly1305",
+ "xchacha20-ietf-poly1305",
+ "aes-128-pmac-siv",
+ "aes-256-pmac-siv",
+]