summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadMockData/MullvadREST/DevicesProxy+Stubs.swift2
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTDevicesProxy.swift38
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift18
-rw-r--r--ios/MullvadREST/MullvadAPI/APIHandlers/MullvadDeviceProxy.swift147
-rw-r--r--ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift45
-rw-r--r--ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift44
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h113
-rw-r--r--ios/MullvadTypes/RESTTypes.swift40
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/TunnelManager/SetAccountOperation.swift2
-rw-r--r--mullvad-api/src/device.rs134
-rw-r--r--mullvad-ios/src/api_client/device.rs368
-rw-r--r--mullvad-ios/src/api_client/mod.rs19
-rw-r--r--mullvad-ios/src/api_client/problem_report.rs11
14 files changed, 883 insertions, 102 deletions
diff --git a/ios/MullvadMockData/MullvadREST/DevicesProxy+Stubs.swift b/ios/MullvadMockData/MullvadREST/DevicesProxy+Stubs.swift
index 6748805b2c..acf3d6369a 100644
--- a/ios/MullvadMockData/MullvadREST/DevicesProxy+Stubs.swift
+++ b/ios/MullvadMockData/MullvadREST/DevicesProxy+Stubs.swift
@@ -41,7 +41,7 @@ struct DevicesProxyStub: DeviceHandling {
func createDevice(
accountNumber: String,
- request: REST.CreateDeviceRequest,
+ request: CreateDeviceRequest,
retryStrategy: REST.RetryStrategy,
completion: @escaping ProxyCompletionHandler<Device>
) -> Cancellable {
diff --git a/ios/MullvadREST/ApiHandlers/RESTDevicesProxy.swift b/ios/MullvadREST/ApiHandlers/RESTDevicesProxy.swift
index 20dd0d0539..5a6d369ef3 100644
--- a/ios/MullvadREST/ApiHandlers/RESTDevicesProxy.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTDevicesProxy.swift
@@ -26,7 +26,7 @@ public protocol DeviceHandling: Sendable {
func createDevice(
accountNumber: String,
- request: REST.CreateDeviceRequest,
+ request: CreateDeviceRequest,
retryStrategy: REST.RetryStrategy,
completion: @escaping @Sendable ProxyCompletionHandler<Device>
) -> Cancellable
@@ -309,40 +309,4 @@ extension REST {
return executor.execute(retryStrategy: retryStrategy, completionHandler: completion)
}
}
-
- public struct CreateDeviceRequest: Encodable, Sendable {
- let publicKey: PublicKey
- let hijackDNS: Bool
-
- public init(publicKey: PublicKey, hijackDNS: Bool) {
- self.publicKey = publicKey
- self.hijackDNS = hijackDNS
- }
-
- private enum CodingKeys: String, CodingKey {
- case hijackDNS = "hijackDns"
- case publicKey = "pubkey"
- }
-
- public func encode(to encoder: Encoder) throws {
- var container = encoder.container(keyedBy: CodingKeys.self)
-
- try container.encode(publicKey.base64Key, forKey: .publicKey)
- try container.encode(hijackDNS, forKey: .hijackDNS)
- }
- }
-
- private struct RotateDeviceKeyRequest: Encodable, Sendable {
- let publicKey: PublicKey
-
- private enum CodingKeys: String, CodingKey {
- case publicKey = "pubkey"
- }
-
- func encode(to encoder: Encoder) throws {
- var container = encoder.container(keyedBy: CodingKeys.self)
-
- try container.encode(publicKey.base64Key, forKey: .publicKey)
- }
- }
}
diff --git a/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift b/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift
index ff3751c5bd..4d6f5eaa02 100644
--- a/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift
@@ -71,11 +71,29 @@ extension REST {
}
public func createAccountsProxy() -> RESTAccountHandling {
+ #if DEBUG
+ MullvadAccountProxy(
+ transportProvider: configuration.apiTransportProvider,
+ dispatchQueue: DispatchQueue(label: "MullvadAccountProxy.dispatchQueue"),
+ responseDecoder: Coding.makeJSONDecoder()
+ )
+
+ #else
REST.AccountsProxy(configuration: configuration)
+ #endif
}
public func createDevicesProxy() -> DeviceHandling {
+ #if DEBUG
+ MullvadDeviceProxy(
+ transportProvider: configuration.apiTransportProvider,
+ dispatchQueue: DispatchQueue(label: "MullvadDeviceProxy.dispatchQueue"),
+ responseDecoder: Coding.makeJSONDecoder()
+ )
+
+ #else
REST.DevicesProxy(configuration: configuration)
+ #endif
}
}
}
diff --git a/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadDeviceProxy.swift b/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadDeviceProxy.swift
new file mode 100644
index 0000000000..f2bdb027cf
--- /dev/null
+++ b/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadDeviceProxy.swift
@@ -0,0 +1,147 @@
+//
+// MullvadDeviceProxy.swift
+// MullvadVPN
+//
+// Created by Mojgan on 2025-04-02.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+import MullvadRustRuntime
+import MullvadTypes
+import Operations
+import WireGuardKitTypes
+
+extension REST {
+ final class MullvadDeviceProxy: DeviceHandling, @unchecked Sendable {
+ let transportProvider: APITransportProviderProtocol
+ let dispatchQueue: DispatchQueue
+ let operationQueue = AsyncOperationQueue()
+ let responseDecoder: JSONDecoder
+
+ public init(
+ transportProvider: APITransportProviderProtocol,
+ dispatchQueue: DispatchQueue,
+ responseDecoder: JSONDecoder
+ ) {
+ self.transportProvider = transportProvider
+ self.dispatchQueue = dispatchQueue
+ self.responseDecoder = responseDecoder
+ }
+
+ func getDevice(
+ accountNumber: String,
+ identifier: String,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Device>
+ ) -> Cancellable {
+ let responseHandler = rustResponseHandler(
+ decoding: Device.self,
+ with: responseDecoder
+ )
+
+ return createNetworkOperation(
+ request: .getDevice(retryStrategy, accountNumber: accountNumber, identifier: identifier),
+ responseHandler: responseHandler,
+ completionHandler: completion
+ )
+ }
+
+ func getDevices(
+ accountNumber: String,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<[Device]>
+ ) -> Cancellable {
+ let responseHandler = rustResponseHandler(
+ decoding: [Device].self,
+ with: responseDecoder
+ )
+
+ return createNetworkOperation(
+ request: .getDevices(retryStrategy, accountNumber: accountNumber),
+ responseHandler: responseHandler,
+ completionHandler: completion
+ )
+ }
+
+ func createDevice(
+ accountNumber: String,
+ request: CreateDeviceRequest,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Device>
+ ) -> Cancellable {
+ let responseHandler = rustResponseHandler(
+ decoding: Device.self,
+ with: responseDecoder
+ )
+
+ return createNetworkOperation(
+ request: .createDevice(retryStrategy, accountNumber: accountNumber, request: request),
+ responseHandler: responseHandler,
+ completionHandler: completion
+ )
+ }
+
+ func deleteDevice(
+ accountNumber: String,
+ identifier: String,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Bool>
+ ) -> Cancellable {
+ let responseHandler = rustEmptyResponseHandler()
+
+ return createNetworkOperation(
+ request: .deleteDevice(retryStrategy, accountNumber: accountNumber, identifier: identifier),
+ responseHandler: responseHandler
+ ) { result in
+ if case let .failure(err) = result {
+ completion(.failure(err))
+ } else {
+ completion(.success(true))
+ }
+ }
+ }
+
+ func rotateDeviceKey(
+ accountNumber: String,
+ identifier: String,
+ publicKey: PublicKey,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping ProxyCompletionHandler<Device>
+ ) -> Cancellable {
+ let responseHandler = rustResponseHandler(
+ decoding: Device.self,
+ with: responseDecoder
+ )
+
+ return createNetworkOperation(
+ request: .rotateDeviceKey(
+ retryStrategy,
+ accountNumber: accountNumber,
+ identifier: identifier,
+ publicKey: publicKey
+ ),
+ responseHandler: responseHandler,
+ completionHandler: completion
+ )
+ }
+
+ private func createNetworkOperation<Success: Any>(
+ request: APIRequest,
+ responseHandler: RustResponseHandler<Success>,
+ completionHandler: @escaping @Sendable ProxyCompletionHandler<Success>
+ ) -> MullvadApiNetworkOperation<Success> {
+ let networkOperation = MullvadApiNetworkOperation(
+ name: request.name,
+ dispatchQueue: dispatchQueue,
+ request: request,
+ transportProvider: transportProvider,
+ responseDecoder: responseDecoder,
+ responseHandler: responseHandler,
+ completionHandler: completionHandler
+ )
+
+ operationQueue.addOperation(networkOperation)
+
+ return networkOperation
+ }
+ }
+}
diff --git a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift
index 92b6bb89b1..ab1087fe5e 100644
--- a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift
+++ b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift
@@ -6,16 +6,31 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import MullvadTypes
+@preconcurrency import WireGuardKitTypes
public enum APIRequest: Codable, Sendable {
+ // Api Proxy
case getAddressList(_ retryStrategy: REST.RetryStrategy)
case getRelayList(_ retryStrategy: REST.RetryStrategy, etag: String?)
case sendProblemReport(_ retryStrategy: REST.RetryStrategy, problemReportRequest: ProblemReportRequest)
+ // Account Proxy
case createAccount(_ retryStrategy: REST.RetryStrategy)
case getAccount(_ retryStrategy: REST.RetryStrategy, accountNumber: String)
case deleteAccount(_ retryStrategy: REST.RetryStrategy, accountNumber: String)
+ // Device Proxy
+ case getDevice(_ retryStrategy: REST.RetryStrategy, accountNumber: String, identifier: String)
+ case getDevices(_ retryStrategy: REST.RetryStrategy, accountNumber: String)
+ case createDevice(_ retryStrategy: REST.RetryStrategy, accountNumber: String, request: CreateDeviceRequest)
+ case deleteDevice(_ retryStrategy: REST.RetryStrategy, accountNumber: String, identifier: String)
+ case rotateDeviceKey(
+ _ retryStrategy: REST.RetryStrategy,
+ accountNumber: String,
+ identifier: String,
+ publicKey: PublicKey
+ )
+
var name: String {
switch self {
case .getAddressList:
@@ -30,19 +45,33 @@ public enum APIRequest: Codable, Sendable {
"get-account"
case .deleteAccount:
"delete-account"
+ case .getDevice:
+ "get-device"
+ case .getDevices:
+ "get-devices"
+ case .deleteDevice:
+ "delete-device"
+ case .rotateDeviceKey:
+ "rotate-device-key"
+ case .createDevice:
+ "create-device"
}
}
var retryStrategy: REST.RetryStrategy {
switch self {
- case
- let .getAddressList(strategy),
- let .getRelayList(strategy, _),
- let .sendProblemReport(strategy, _),
- let .createAccount(strategy),
- let .getAccount(strategy, _),
- let .deleteAccount(strategy, _):
- strategy
+ case let .getAddressList(strategy),
+ let .getRelayList(strategy, _),
+ let .sendProblemReport(strategy, _),
+ let .createAccount(strategy),
+ let .getAccount(strategy, _),
+ let .deleteAccount(strategy, _),
+ let .createDevice(strategy, _, _),
+ let .getDevice(strategy, _, _),
+ let .getDevices(strategy, _),
+ let .deleteDevice(strategy, _, _),
+ let .rotateDeviceKey(strategy, _, _, _):
+ return strategy
}
}
}
diff --git a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift
index 0b3271c80b..3f29468d9b 100644
--- a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift
+++ b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift
@@ -67,6 +67,50 @@ public struct MullvadApiRequestFactory: Sendable {
retryStrategy.toRustStrategy(),
accountNumber
))
+
+ // Device Proxy
+ case let .getDevice(retryStrategy, accountNumber: accountNumber, identifier):
+ return MullvadApiCancellable(handle: mullvad_ios_get_device(
+ apiContext.context,
+ rawCompletionPointer,
+ retryStrategy.toRustStrategy(),
+ accountNumber,
+ identifier
+ ))
+
+ case let .getDevices(retryStrategy, accountNumber):
+ return MullvadApiCancellable(handle: mullvad_ios_get_devices(
+ apiContext.context,
+ rawCompletionPointer,
+ retryStrategy.toRustStrategy(),
+ accountNumber
+ ))
+
+ case let .deleteDevice(retryStrategy, accountNumber, identifier):
+ return MullvadApiCancellable(handle: mullvad_ios_delete_device(
+ apiContext.context,
+ rawCompletionPointer,
+ retryStrategy.toRustStrategy(),
+ accountNumber,
+ identifier
+ ))
+ case let .rotateDeviceKey(retryStrategy, accountNumber, identifier, publicKey):
+ return MullvadApiCancellable(handle: mullvad_ios_rotate_device_key(
+ apiContext.context,
+ rawCompletionPointer,
+ retryStrategy.toRustStrategy(),
+ accountNumber,
+ identifier,
+ publicKey.rawValue.map { $0 }
+ ))
+ case let .createDevice(retryStrategy, accountNumber, request):
+ return MullvadApiCancellable(handle: mullvad_ios_create_device(
+ apiContext.context,
+ rawCompletionPointer,
+ retryStrategy.toRustStrategy(),
+ accountNumber,
+ request.publicKey.rawValue.map { $0 }
+ ))
}
}
}
diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
index 6300b04902..b4f4a65e2f 100644
--- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
+++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
@@ -361,6 +361,119 @@ extern void mullvad_api_completion_finish(struct SwiftMullvadApiResponse respons
struct CompletionCookie completion_cookie);
/**
+ * Get device info via the Mullvad API client.
+ *
+ * # Safety
+ *
+ * `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+ * by calling `mullvad_ios_init_new`.
+ *
+ * This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+ * object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_ios_completion_finish`
+ * when completion finishes (in completion.finish).
+ *
+ * the `account_number` must be a pointer to a null terminated string.
+ * the `identifier` must be a pointer to a null terminated string.
+ *
+ * This function is not safe to call multiple times with the same `CompletionCookie`.
+ */
+struct SwiftCancelHandle mullvad_ios_get_device(struct SwiftApiContext api_context,
+ void *completion_cookie,
+ struct SwiftRetryStrategy retry_strategy,
+ const char *account_number,
+ const char *identifier);
+
+/**
+ * Get devices info via the Mullvad API client.
+ *
+ * # Safety
+ *
+ * `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+ * by calling `mullvad_api_init_new`.
+ *
+ * This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+ * object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
+ * when completion finishes (in completion.finish).
+ *
+ * the `account_number` must be a pointer to a null terminated string.
+ *
+ * This function is not safe to call multiple times with the same `CompletionCookie`.
+ */
+struct SwiftCancelHandle mullvad_ios_get_devices(struct SwiftApiContext api_context,
+ void *completion_cookie,
+ struct SwiftRetryStrategy retry_strategy,
+ const char *account_number);
+
+/**
+ * create device via the Mullvad API client.
+ *
+ * # Safety
+ *
+ * `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+ * by calling `mullvad_api_init_new`.
+ *
+ * This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+ * object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
+ * when completion finishes (in completion.finish).
+ *
+ * the `account_number` must be a pointer to a null terminated string.
+ * the `identifier` must be a pointer to a null terminated string.
+ * the `public_key` pointer must be a valid pointer to 32 unsigned bytes.
+ * This function is not safe to call multiple times with the same `CompletionCookie`.
+ */
+struct SwiftCancelHandle mullvad_ios_create_device(struct SwiftApiContext api_context,
+ void *completion_cookie,
+ struct SwiftRetryStrategy retry_strategy,
+ const char *account_number,
+ const uint8_t *public_key);
+
+/**
+ * delete device via the Mullvad API client.
+ *
+ * # Safety
+ *
+ * `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+ * by calling `mullvad_api_init_new`.
+ *
+ * This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+ * object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
+ * when completion finishes (in completion.finish).
+ *
+ * the `account_number` must be a pointer to a null terminated string.
+ * the `identifier` must be a pointer to a null terminated string.
+ * This function is not safe to call multiple times with the same `CompletionCookie`.
+ */
+struct SwiftCancelHandle mullvad_ios_delete_device(struct SwiftApiContext api_context,
+ void *completion_cookie,
+ struct SwiftRetryStrategy retry_strategy,
+ const char *account_number,
+ const char *identifier);
+
+/**
+ * rotate device key via the Mullvad API client.
+ *
+ * # Safety
+ *
+ * `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+ * by calling `mullvad_api_init_new`.
+ *
+ * This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+ * object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
+ * when completion finishes (in completion.finish).
+ *
+ * the `account_number` must be a pointer to a null terminated string.
+ * the `identifier` must be a pointer to a null terminated string.
+ * the `public_key` pointer must be a valid pointer to 32 unsigned bytes.
+ * This function is not safe to call multiple times with the same `CompletionCookie`.
+ */
+struct SwiftCancelHandle mullvad_ios_rotate_device_key(struct SwiftApiContext api_context,
+ void *completion_cookie,
+ struct SwiftRetryStrategy retry_strategy,
+ const char *account_number,
+ const char *identifier,
+ const uint8_t *public_key);
+
+/**
* Converts parameters into a boxed `Shadowsocks` configuration that is safe
* to send across the FFI boundary
*
diff --git a/ios/MullvadTypes/RESTTypes.swift b/ios/MullvadTypes/RESTTypes.swift
index 6ba5c08297..dee960ca27 100644
--- a/ios/MullvadTypes/RESTTypes.swift
+++ b/ios/MullvadTypes/RESTTypes.swift
@@ -69,3 +69,43 @@ public struct ProblemReportRequest: Codable, Sendable {
self.metadata = metadata
}
}
+
+public struct CreateDeviceRequest: Codable, Sendable {
+ public let publicKey: PublicKey
+ public let hijackDNS: Bool
+
+ public init(publicKey: PublicKey, hijackDNS: Bool) {
+ self.publicKey = publicKey
+ self.hijackDNS = hijackDNS
+ }
+
+ private enum CodingKeys: String, CodingKey {
+ case hijackDNS = "hijackDns"
+ case publicKey = "pubkey"
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+
+ try container.encode(publicKey.base64Key, forKey: .publicKey)
+ try container.encode(hijackDNS, forKey: .hijackDNS)
+ }
+}
+
+public struct RotateDeviceKeyRequest: Codable, Sendable {
+ let publicKey: PublicKey
+
+ public init(publicKey: PublicKey) {
+ self.publicKey = publicKey
+ }
+
+ private enum CodingKeys: String, CodingKey {
+ case publicKey = "pubkey"
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+
+ try container.encode(publicKey.base64Key, forKey: .publicKey)
+ }
+}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 26406d4230..9d291a7ae5 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -1026,6 +1026,7 @@
F0A086902C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A0868F2C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift */; };
F0A7EBB22CEF6C79005BB671 /* ConsolidatedApplicationLogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A7EBB12CEF6C79005BB671 /* ConsolidatedApplicationLogTests.swift */; };
F0A7EBB62CF092CC005BB671 /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; };
+ F0A89CB32D9D6C2100580C27 /* MullvadDeviceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A89CB22D9D6C1400580C27 /* MullvadDeviceProxy.swift */; };
F0A89CB52D9D864B00580C27 /* RustProblemReportRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0EEFB9E2D8D60E1007FE4B3 /* RustProblemReportRequest.swift */; };
F0A89CB72D9D923300580C27 /* String+UnsafePointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A89CB62D9D922300580C27 /* String+UnsafePointer.swift */; };
F0ACE30D2BE4E478006D5333 /* MullvadMockData.h in Headers */ = {isa = PBXBuildFile; fileRef = F0ACE30A2BE4E478006D5333 /* MullvadMockData.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -2454,6 +2455,7 @@
F0A0868F2C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsStrategyTests.swift; sourceTree = "<group>"; };
F0A163882C47B46300592300 /* SingleHopEphemeralPeerExchangerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleHopEphemeralPeerExchangerTests.swift; sourceTree = "<group>"; };
F0A7EBB12CEF6C79005BB671 /* ConsolidatedApplicationLogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsolidatedApplicationLogTests.swift; sourceTree = "<group>"; };
+ F0A89CB22D9D6C1400580C27 /* MullvadDeviceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadDeviceProxy.swift; sourceTree = "<group>"; };
F0A89CB62D9D922300580C27 /* String+UnsafePointer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+UnsafePointer.swift"; sourceTree = "<group>"; };
F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadMockData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F0ACE30A2BE4E478006D5333 /* MullvadMockData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadMockData.h; sourceTree = "<group>"; };
@@ -4223,6 +4225,7 @@
children = (
7AB73F6D2D9AAD0400DA5E1D /* MullvadAccountProxy.swift */,
7A2C0E8B2D8B13E8003D8048 /* MullvadAPIProxy.swift */,
+ F0A89CB22D9D6C1400580C27 /* MullvadDeviceProxy.swift */,
);
path = APIHandlers;
sourceTree = "<group>";
@@ -5854,6 +5857,7 @@
A9D99B9A2A1F7C3200DE27D3 /* RESTTransport.swift in Sources */,
A90763BB2B2857D50045ADF0 /* Socks5AddressType.swift in Sources */,
7A99D36F2D56070400891FF7 /* MullvadApiRequestFactory.swift in Sources */,
+ F0A89CB32D9D6C2100580C27 /* MullvadDeviceProxy.swift in Sources */,
F0F3161B2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift in Sources */,
06799AE028F98E4800ACD94E /* RESTCoding.swift in Sources */,
A90763B72B2857D50045ADF0 /* Socks5DataStreamHandler.swift in Sources */,
diff --git a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
index 07467a5ca8..c018078989 100644
--- a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
@@ -392,7 +392,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?>, @unchecked Senda
completion: @escaping @Sendable (Result<NewDevice, Error>) -> Void
) {
let privateKey = PrivateKey()
- let request = REST.CreateDeviceRequest(publicKey: privateKey.publicKey, hijackDNS: false)
+ let request = CreateDeviceRequest(publicKey: privateKey.publicKey, hijackDNS: false)
logger.debug("Create device...")
diff --git a/mullvad-api/src/device.rs b/mullvad-api/src/device.rs
index 062c06f28b..770c86a642 100644
--- a/mullvad-api/src/device.rs
+++ b/mullvad-api/src/device.rs
@@ -1,5 +1,6 @@
use chrono::{DateTime, Utc};
use http::StatusCode;
+use hyper::body::Incoming;
use mullvad_types::{
account::AccountNumber,
device::{Device, DeviceId, DeviceName},
@@ -39,26 +40,9 @@ impl DevicesProxy {
) -> impl Future<
Output = Result<(Device, mullvad_types::wireguard::AssociatedAddresses), rest::Error>,
> + use<> {
- #[derive(serde::Serialize)]
- struct DeviceSubmission {
- pubkey: wireguard::PublicKey,
- hijack_dns: bool,
- }
-
- let submission = DeviceSubmission {
- pubkey,
- hijack_dns: false,
- };
-
- let service = self.handle.service.clone();
- let factory = self.handle.factory.clone();
+ let request = self.create_response(account, pubkey);
async move {
- let request = factory
- .post_json(&format!("{ACCOUNTS_URL_PREFIX}/devices"), &submission)?
- .account(account)?
- .expected_status(&[StatusCode::CREATED]);
- let response = service.request(request).await?;
let DeviceResponse {
id,
name,
@@ -68,7 +52,7 @@ impl DevicesProxy {
hijack_dns,
created,
..
- } = response.deserialize().await?;
+ } = request.await?.deserialize().await?;
Ok((
Device {
@@ -91,14 +75,10 @@ impl DevicesProxy {
account: AccountNumber,
id: DeviceId,
) -> impl Future<Output = Result<Device, rest::Error>> + use<> {
- let service = self.handle.service.clone();
- let factory = self.handle.factory.clone();
+ let request = self.get_response(account, id);
async move {
- let request = factory
- .get(&format!("{ACCOUNTS_URL_PREFIX}/devices/{id}"))?
- .expected_status(&[StatusCode::OK])
- .account(account)?;
- service.request(request).await?.deserialize().await
+ let data = request.await?.deserialize().await?;
+ Ok(data)
}
}
@@ -106,14 +86,10 @@ impl DevicesProxy {
&self,
account: AccountNumber,
) -> impl Future<Output = Result<Vec<Device>, rest::Error>> + use<> {
- let service = self.handle.service.clone();
- let factory = self.handle.factory.clone();
+ let request = self.list_response(account);
async move {
- let request = factory
- .get(&format!("{ACCOUNTS_URL_PREFIX}/devices"))?
- .expected_status(&[StatusCode::OK])
- .account(account)?;
- service.request(request).await?.deserialize().await
+ let data = request.await?.deserialize().await?;
+ Ok(data)
}
}
@@ -141,6 +117,59 @@ impl DevicesProxy {
pubkey: wireguard::PublicKey,
) -> impl Future<Output = Result<mullvad_types::wireguard::AssociatedAddresses, rest::Error>> + use<>
{
+ let request = self.replace_wg_key_response(account, id, pubkey);
+ async move {
+ let DeviceResponse {
+ ipv4_address,
+ ipv6_address,
+ ..
+ } = request.await?.deserialize().await?;
+ Ok(mullvad_types::wireguard::AssociatedAddresses {
+ ipv4_address,
+ ipv6_address,
+ })
+ }
+ }
+
+ pub fn get_response(
+ &self,
+ account: AccountNumber,
+ id: DeviceId,
+ ) -> impl Future<Output = Result<rest::Response<Incoming>, rest::Error>> {
+ let service = self.handle.service.clone();
+ let factory = self.handle.factory.clone();
+
+ async move {
+ let request = factory
+ .get(&format!("{ACCOUNTS_URL_PREFIX}/devices/{id}"))?
+ .expected_status(&[StatusCode::OK])
+ .account(account)?;
+ service.request(request).await
+ }
+ }
+
+ pub fn list_response(
+ &self,
+ account: AccountNumber,
+ ) -> impl Future<Output = Result<rest::Response<Incoming>, rest::Error>> {
+ let service = self.handle.service.clone();
+ let factory = self.handle.factory.clone();
+
+ async move {
+ let request = factory
+ .get(&format!("{ACCOUNTS_URL_PREFIX}/devices"))?
+ .expected_status(&[StatusCode::OK])
+ .account(account)?;
+ service.request(request).await
+ }
+ }
+
+ pub fn replace_wg_key_response(
+ &self,
+ account: AccountNumber,
+ id: DeviceId,
+ pubkey: wireguard::PublicKey,
+ ) -> impl Future<Output = Result<rest::Response<Incoming>, rest::Error>> {
#[derive(serde::Serialize)]
struct RotateDevicePubkey {
pubkey: wireguard::PublicKey,
@@ -158,16 +187,35 @@ impl DevicesProxy {
)?
.expected_status(&[StatusCode::OK])
.account(account)?;
- let response = service.request(request).await?;
- let DeviceResponse {
- ipv4_address,
- ipv6_address,
- ..
- } = response.deserialize().await?;
- Ok(mullvad_types::wireguard::AssociatedAddresses {
- ipv4_address,
- ipv6_address,
- })
+ service.request(request).await
+ }
+ }
+
+ pub fn create_response(
+ &self,
+ account: AccountNumber,
+ pubkey: wireguard::PublicKey,
+ ) -> impl Future<Output = Result<rest::Response<Incoming>, rest::Error>> {
+ #[derive(serde::Serialize)]
+ struct DeviceSubmission {
+ pubkey: wireguard::PublicKey,
+ hijack_dns: bool,
+ }
+
+ let submission = DeviceSubmission {
+ pubkey,
+ hijack_dns: false,
+ };
+
+ let service = self.handle.service.clone();
+ let factory = self.handle.factory.clone();
+
+ async move {
+ let request = factory
+ .post_json(&format!("{ACCOUNTS_URL_PREFIX}/devices"), &submission)?
+ .account(account)?
+ .expected_status(&[StatusCode::CREATED]);
+ service.request(request).await
}
}
}
diff --git a/mullvad-ios/src/api_client/device.rs b/mullvad-ios/src/api_client/device.rs
new file mode 100644
index 0000000000..8ca287ebb2
--- /dev/null
+++ b/mullvad-ios/src/api_client/device.rs
@@ -0,0 +1,368 @@
+use libc::c_char;
+use mullvad_api::{
+ rest::{self, MullvadRestHandle},
+ DevicesProxy,
+};
+
+use super::{
+ cancellation::{RequestCancelHandle, SwiftCancelHandle},
+ completion::{CompletionCookie, SwiftCompletionHandler},
+ do_request, do_request_with_empty_body, get_string,
+ response::SwiftMullvadApiResponse,
+ retry_strategy::{RetryStrategy, SwiftRetryStrategy},
+ SwiftApiContext,
+};
+use std::ptr;
+use talpid_types::net::wireguard;
+use talpid_types::net::wireguard::PublicKey;
+
+/// Get device info via the Mullvad API client.
+///
+/// # Safety
+///
+/// `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+/// by calling `mullvad_ios_init_new`.
+///
+/// This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+/// object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_ios_completion_finish`
+/// when completion finishes (in completion.finish).
+///
+/// the `account_number` must be a pointer to a null terminated string.
+/// the `identifier` must be a pointer to a null terminated string.
+///
+/// This function is not safe to call multiple times with the same `CompletionCookie`.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn mullvad_ios_get_device(
+ api_context: SwiftApiContext,
+ completion_cookie: *mut libc::c_void,
+ retry_strategy: SwiftRetryStrategy,
+ account_number: *const c_char,
+ identifier: *const c_char,
+) -> SwiftCancelHandle {
+ let completion_handler = SwiftCompletionHandler::new(CompletionCookie::new(completion_cookie));
+
+ let Ok(tokio_handle) = crate::mullvad_ios_runtime() else {
+ completion_handler.finish(SwiftMullvadApiResponse::no_tokio_runtime());
+ return SwiftCancelHandle::empty();
+ };
+
+ let api_context = api_context.rust_context();
+ // Safety: The caller must guarantee that `retry_strategy` is not null and has not been freed
+ let retry_strategy = unsafe { retry_strategy.into_rust() };
+ let account_number = get_string(account_number);
+ let identifier = get_string(identifier);
+
+ let completion = completion_handler.clone();
+ let task = tokio_handle.spawn(async move {
+ match mullvad_ios_get_device_inner(
+ api_context.rest_handle(),
+ retry_strategy,
+ account_number,
+ identifier,
+ )
+ .await
+ {
+ Ok(response) => completion.finish(response),
+ Err(err) => {
+ log::error!("{err:?}");
+ completion.finish(SwiftMullvadApiResponse::rest_error(err));
+ }
+ }
+ });
+
+ RequestCancelHandle::new(task, completion_handler).into_swift()
+}
+
+/// Get devices info via the Mullvad API client.
+///
+/// # Safety
+///
+/// `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+/// by calling `mullvad_api_init_new`.
+///
+/// This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+/// object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
+/// when completion finishes (in completion.finish).
+///
+/// the `account_number` must be a pointer to a null terminated string.
+///
+/// This function is not safe to call multiple times with the same `CompletionCookie`.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn mullvad_ios_get_devices(
+ api_context: SwiftApiContext,
+ completion_cookie: *mut libc::c_void,
+ retry_strategy: SwiftRetryStrategy,
+ account_number: *const c_char,
+) -> SwiftCancelHandle {
+ let completion_handler = SwiftCompletionHandler::new(CompletionCookie::new(completion_cookie));
+
+ let Ok(tokio_handle) = crate::mullvad_ios_runtime() else {
+ completion_handler.finish(SwiftMullvadApiResponse::no_tokio_runtime());
+ return SwiftCancelHandle::empty();
+ };
+
+ let api_context = api_context.rust_context();
+ // Safety: The caller must guarantee that `retry_strategy` is not null and has not been freed
+ let retry_strategy = unsafe { retry_strategy.into_rust() };
+ let account_number = get_string(account_number);
+
+ let completion = completion_handler.clone();
+ let task = tokio_handle.spawn(async move {
+ match mullvad_ios_get_devices_inner(
+ api_context.rest_handle(),
+ retry_strategy,
+ account_number,
+ )
+ .await
+ {
+ Ok(response) => completion.finish(response),
+ Err(err) => {
+ log::error!("{err:?}");
+ completion.finish(SwiftMullvadApiResponse::rest_error(err));
+ }
+ }
+ });
+
+ RequestCancelHandle::new(task, completion_handler).into_swift()
+}
+
+/// create device via the Mullvad API client.
+///
+/// # Safety
+///
+/// `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+/// by calling `mullvad_api_init_new`.
+///
+/// This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+/// object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
+/// when completion finishes (in completion.finish).
+///
+/// the `account_number` must be a pointer to a null terminated string.
+/// the `identifier` must be a pointer to a null terminated string.
+/// the `public_key` pointer must be a valid pointer to 32 unsigned bytes.
+/// This function is not safe to call multiple times with the same `CompletionCookie`.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn mullvad_ios_create_device(
+ api_context: SwiftApiContext,
+ completion_cookie: *mut libc::c_void,
+ retry_strategy: SwiftRetryStrategy,
+ account_number: *const c_char,
+ public_key: *const u8,
+) -> SwiftCancelHandle {
+ let completion_handler = SwiftCompletionHandler::new(CompletionCookie::new(completion_cookie));
+
+ let Ok(tokio_handle) = crate::mullvad_ios_runtime() else {
+ completion_handler.finish(SwiftMullvadApiResponse::no_tokio_runtime());
+ return SwiftCancelHandle::empty();
+ };
+
+ let api_context = api_context.rust_context();
+ // Safety: The caller must guarantee that `retry_strategy` is not null and has not been freed
+ let retry_strategy = unsafe { retry_strategy.into_rust() };
+ let account_number = get_string(account_number);
+ // Safety: `public_key` pointer must be a valid pointer to 32 unsigned bytes.
+ let pub_key: [u8; 32] = unsafe { ptr::read(public_key as *const [u8; 32]) };
+
+ let completion = completion_handler.clone();
+ let task = tokio_handle.spawn(async move {
+ match mullvad_ios_create_device_inner(
+ api_context.rest_handle(),
+ retry_strategy,
+ account_number,
+ PublicKey::from(pub_key),
+ )
+ .await
+ {
+ Ok(response) => completion.finish(response),
+ Err(err) => {
+ log::error!("{err:?}");
+ completion.finish(SwiftMullvadApiResponse::rest_error(err));
+ }
+ }
+ });
+
+ RequestCancelHandle::new(task, completion_handler).into_swift()
+}
+
+/// delete device via the Mullvad API client.
+///
+/// # Safety
+///
+/// `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+/// by calling `mullvad_api_init_new`.
+///
+/// This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+/// object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
+/// when completion finishes (in completion.finish).
+///
+/// the `account_number` must be a pointer to a null terminated string.
+/// the `identifier` must be a pointer to a null terminated string.
+/// This function is not safe to call multiple times with the same `CompletionCookie`.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn mullvad_ios_delete_device(
+ api_context: SwiftApiContext,
+ completion_cookie: *mut libc::c_void,
+ retry_strategy: SwiftRetryStrategy,
+ account_number: *const c_char,
+ identifier: *const c_char,
+) -> SwiftCancelHandle {
+ let completion_handler = SwiftCompletionHandler::new(CompletionCookie::new(completion_cookie));
+
+ let Ok(tokio_handle) = crate::mullvad_ios_runtime() else {
+ completion_handler.finish(SwiftMullvadApiResponse::no_tokio_runtime());
+ return SwiftCancelHandle::empty();
+ };
+
+ let api_context = api_context.rust_context();
+ // Safety: The caller must guarantee that `retry_strategy` is not null and has not been freed
+ let retry_strategy = unsafe { retry_strategy.into_rust() };
+ let account_number = get_string(account_number);
+ let identifier = get_string(identifier);
+
+ let completion = completion_handler.clone();
+ let task = tokio_handle.spawn(async move {
+ match mullvad_ios_delete_device_inner(
+ api_context.rest_handle(),
+ retry_strategy,
+ account_number,
+ identifier,
+ )
+ .await
+ {
+ Ok(response) => completion.finish(response),
+ Err(err) => {
+ log::error!("{err:?}");
+ completion.finish(SwiftMullvadApiResponse::rest_error(err));
+ }
+ }
+ });
+
+ RequestCancelHandle::new(task, completion_handler).into_swift()
+}
+
+/// rotate device key via the Mullvad API client.
+///
+/// # Safety
+///
+/// `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created
+/// by calling `mullvad_api_init_new`.
+///
+/// This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift
+/// object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish`
+/// when completion finishes (in completion.finish).
+///
+/// the `account_number` must be a pointer to a null terminated string.
+/// the `identifier` must be a pointer to a null terminated string.
+/// the `public_key` pointer must be a valid pointer to 32 unsigned bytes.
+/// This function is not safe to call multiple times with the same `CompletionCookie`.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn mullvad_ios_rotate_device_key(
+ api_context: SwiftApiContext,
+ completion_cookie: *mut libc::c_void,
+ retry_strategy: SwiftRetryStrategy,
+ account_number: *const c_char,
+ identifier: *const c_char,
+ public_key: *const u8,
+) -> SwiftCancelHandle {
+ let completion_handler = SwiftCompletionHandler::new(CompletionCookie::new(completion_cookie));
+
+ let Ok(tokio_handle) = crate::mullvad_ios_runtime() else {
+ completion_handler.finish(SwiftMullvadApiResponse::no_tokio_runtime());
+ return SwiftCancelHandle::empty();
+ };
+
+ let api_context = api_context.rust_context();
+ // Safety: The caller must guarantee that `retry_strategy` is not null and has not been freed
+ let retry_strategy = unsafe { retry_strategy.into_rust() };
+ let account_number = get_string(account_number);
+ let identifier = get_string(identifier);
+ // Safety: `public_key` pointer must be a valid pointer to 32 unsigned bytes.
+ let pub_key: [u8; 32] = unsafe { ptr::read(public_key as *const [u8; 32]) };
+
+ let completion = completion_handler.clone();
+ let task = tokio_handle.spawn(async move {
+ match mullvad_ios_rotate_device_key_inner(
+ api_context.rest_handle(),
+ retry_strategy,
+ account_number,
+ identifier,
+ PublicKey::from(pub_key),
+ )
+ .await
+ {
+ Ok(response) => completion.finish(response),
+ Err(err) => {
+ log::error!("{err:?}");
+ completion.finish(SwiftMullvadApiResponse::rest_error(err));
+ }
+ }
+ });
+
+ RequestCancelHandle::new(task, completion_handler).into_swift()
+}
+
+async fn mullvad_ios_get_device_inner(
+ rest_client: MullvadRestHandle,
+ retry_strategy: RetryStrategy,
+ account_number: String,
+ identifier: String,
+) -> Result<SwiftMullvadApiResponse, rest::Error> {
+ let api = DevicesProxy::new(rest_client);
+
+ let future_factory = || api.get_response(account_number.clone(), identifier.clone());
+
+ do_request(retry_strategy, future_factory).await
+}
+
+async fn mullvad_ios_get_devices_inner(
+ rest_client: MullvadRestHandle,
+ retry_strategy: RetryStrategy,
+ account_number: String,
+) -> Result<SwiftMullvadApiResponse, rest::Error> {
+ let api = DevicesProxy::new(rest_client);
+
+ let future_factory = || api.list_response(account_number.clone());
+
+ do_request(retry_strategy, future_factory).await
+}
+
+async fn mullvad_ios_delete_device_inner(
+ rest_client: MullvadRestHandle,
+ retry_strategy: RetryStrategy,
+ account_number: String,
+ identifier: String,
+) -> Result<SwiftMullvadApiResponse, rest::Error> {
+ let api = DevicesProxy::new(rest_client);
+
+ let future_factory = || api.remove(account_number.clone(), identifier.clone());
+
+ do_request_with_empty_body(retry_strategy, future_factory).await
+}
+
+async fn mullvad_ios_rotate_device_key_inner(
+ rest_client: MullvadRestHandle,
+ retry_strategy: RetryStrategy,
+ account_number: String,
+ identifier: String,
+ pub_key: wireguard::PublicKey,
+) -> Result<SwiftMullvadApiResponse, rest::Error> {
+ let api = DevicesProxy::new(rest_client);
+
+ let future_factory =
+ || api.replace_wg_key_response(account_number.clone(), identifier.clone(), pub_key.clone());
+
+ do_request(retry_strategy, future_factory).await
+}
+
+async fn mullvad_ios_create_device_inner(
+ rest_client: MullvadRestHandle,
+ retry_strategy: RetryStrategy,
+ account_number: String,
+ pub_key: wireguard::PublicKey,
+) -> Result<SwiftMullvadApiResponse, rest::Error> {
+ let api = DevicesProxy::new(rest_client);
+
+ let future_factory = || api.create_response(account_number.clone(), pub_key.clone());
+
+ do_request(retry_strategy, future_factory).await
+}
diff --git a/mullvad-ios/src/api_client/mod.rs b/mullvad-ios/src/api_client/mod.rs
index e8fe1ced24..bf849bc45c 100644
--- a/mullvad-ios/src/api_client/mod.rs
+++ b/mullvad-ios/src/api_client/mod.rs
@@ -1,4 +1,4 @@
-use std::{ffi::c_char, future::Future, sync::Arc};
+use std::{ffi::c_char, ffi::CStr, future::Future, sync::Arc};
use access_method_resolver::SwiftAccessMethodResolver;
use access_method_settings::SwiftAccessMethodSettingsWrapper;
@@ -21,6 +21,7 @@ mod account;
mod api;
mod cancellation;
mod completion;
+mod device;
pub(super) mod helpers;
mod mock;
mod problem_report;
@@ -169,7 +170,7 @@ pub extern "C" fn mullvad_api_init_new(
host,
address,
domain,
- true,
+ false,
bridge_provider,
settings_provider,
);
@@ -297,3 +298,17 @@ where
retry_future(future_factory, should_retry, retry_strategy.delays()).await
}
+
+/// Try to convert a C string to an owned [String]. if `ptr` is null, an empty [String] is
+/// returned.
+///
+/// # Safety
+/// - `ptr` must uphold all safety invariants as required by [CStr::from_ptr].
+fn get_string(ptr: *const c_char) -> String {
+ if ptr.is_null() {
+ return String::new();
+ }
+ // Safety: See function doc comment.
+ let cstr = unsafe { CStr::from_ptr(ptr) };
+ cstr.to_str().map(ToOwned::to_owned).unwrap_or_default()
+}
diff --git a/mullvad-ios/src/api_client/problem_report.rs b/mullvad-ios/src/api_client/problem_report.rs
index 8c47da0eef..3254d7f6d2 100644
--- a/mullvad-ios/src/api_client/problem_report.rs
+++ b/mullvad-ios/src/api_client/problem_report.rs
@@ -8,7 +8,7 @@ use std::os::raw::c_char;
use super::{
cancellation::{RequestCancelHandle, SwiftCancelHandle},
completion::{CompletionCookie, SwiftCompletionHandler},
- do_request_with_empty_body,
+ do_request_with_empty_body, get_string,
response::SwiftMullvadApiResponse,
retry_strategy::{RetryStrategy, SwiftRetryStrategy},
SwiftApiContext,
@@ -119,15 +119,6 @@ struct ProblemReportRequest {
impl ProblemReportRequest {
// SAFETY: the members of `SwiftProblemReportRequest` must point to null-terminated strings
unsafe fn from_swift_parameters(request: SwiftProblemReportRequest) -> Option<Self> {
- fn get_string(ptr: *const c_char) -> String {
- if ptr.is_null() {
- return String::new();
- }
- // Safety: `ptr` must be a valid, null-terminated C string.
- let cstr = unsafe { CStr::from_ptr(ptr) };
- cstr.to_str().map(ToOwned::to_owned).unwrap_or_default()
- }
-
let address = get_string(request.address);
let message = get_string(request.message);
let log = get_string(request.log).into();