summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-04-15 15:53:59 +0200
committerJon Petersson <jon.petersson@mullvad.net>2025-04-15 15:53:59 +0200
commitc90a98e3430c8047b17b12ca9646af6d4478d3ea (patch)
treed0f5fa50299da5bfeb65ed591e6c16c70e5cc4dc /ios
parenteffafb699b8f6831cbc6df0bcaa51766d9a44718 (diff)
parent14db956bcd66b841734ed86b647e95f69034bffe (diff)
downloadmullvadvpn-c90a98e3430c8047b17b12ca9646af6d4478d3ea.tar.xz
mullvadvpn-c90a98e3430c8047b17b12ca9646af6d4478d3ea.zip
Merge branch 'implement-sendproblemreport-using-mullvad-api-ios-1134-2'
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift2
-rw-r--r--ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift24
-rw-r--r--ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift6
-rw-r--r--ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift20
-rw-r--r--ios/MullvadREST/Transport/APITransport.swift1
-rw-r--r--ios/MullvadRustRuntime/RustProblemReportRequest.swift48
-rw-r--r--ios/MullvadRustRuntime/String+UnsafePointer.swift27
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h61
-rw-r--r--ios/MullvadTypes/RESTTypes.swift14
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj24
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift9
-rw-r--r--ios/MullvadVPNTests/MullvadREST/MullvadApi/RustProblemReportRequestTests.swift67
12 files changed, 265 insertions, 38 deletions
diff --git a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift
index 1330be345f..9880512524 100644
--- a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift
+++ b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift
@@ -37,7 +37,7 @@ struct APIProxyStub: APIQuerying {
}
func sendProblemReport(
- _ body: REST.ProblemReportRequest,
+ _ body: ProblemReportRequest,
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping ProxyCompletionHandler<Void>
) -> Cancellable {
diff --git a/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift b/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift
index 5d2555f07f..5cf3ee0e37 100644
--- a/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift
+++ b/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift
@@ -29,7 +29,7 @@ public protocol APIQuerying: Sendable {
) -> any RESTRequestExecutor<REST.CreateApplePaymentResponse>
func sendProblemReport(
- _ body: REST.ProblemReportRequest,
+ _ body: ProblemReportRequest,
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping @Sendable ProxyCompletionHandler<Void>
) -> Cancellable
@@ -122,7 +122,11 @@ extension REST {
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping ProxyCompletionHandler<Void>
) -> Cancellable {
- AnyCancellable()
+ createNetworkOperation(
+ request: .sendProblemReport(retryStrategy, problemReportRequest: body),
+ responseHandler: rustEmptyResponseHandler(),
+ completionHandler: completionHandler
+ )
}
public func submitVoucher(
@@ -134,7 +138,7 @@ extension REST {
AnyCancellable()
}
- private func createNetworkOperation<Success: Decodable>(
+ private func createNetworkOperation<Success: Any>(
request: APIRequest,
responseHandler: RustResponseHandler<Success>,
completionHandler: @escaping @Sendable ProxyCompletionHandler<Success>
@@ -200,20 +204,6 @@ extension REST {
let timeAdded: Int
let newExpiry: Date
}
-
- public struct ProblemReportRequest: Encodable, Sendable {
- public let address: String
- public let message: String
- public let log: String
- public let metadata: [String: String]
-
- public init(address: String, message: String, log: String, metadata: [String: String]) {
- self.address = address
- self.message = message
- self.log = log
- self.metadata = metadata
- }
- }
}
// TODO: Remove when "createApplePayment" func is implemented.
diff --git a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift
index 68e7e41663..92b6bb89b1 100644
--- a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift
+++ b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift
@@ -5,10 +5,13 @@
// Created by Jon Petersson on 2025-02-24.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
+import MullvadTypes
public enum APIRequest: Codable, Sendable {
case getAddressList(_ retryStrategy: REST.RetryStrategy)
case getRelayList(_ retryStrategy: REST.RetryStrategy, etag: String?)
+ case sendProblemReport(_ retryStrategy: REST.RetryStrategy, problemReportRequest: ProblemReportRequest)
+
case createAccount(_ retryStrategy: REST.RetryStrategy)
case getAccount(_ retryStrategy: REST.RetryStrategy, accountNumber: String)
case deleteAccount(_ retryStrategy: REST.RetryStrategy, accountNumber: String)
@@ -19,6 +22,8 @@ public enum APIRequest: Codable, Sendable {
"get-address-list"
case .getRelayList:
"get-relay-list"
+ case .sendProblemReport:
+ "send-problem-report"
case .createAccount:
"create-account"
case .getAccount:
@@ -33,6 +38,7 @@ public enum APIRequest: Codable, Sendable {
case
let .getAddressList(strategy),
let .getRelayList(strategy, _),
+ let .sendProblemReport(strategy, _),
let .createAccount(strategy),
let .getAccount(strategy, _),
let .deleteAccount(strategy, _):
diff --git a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift
index 298d3ac31f..0b3271c80b 100644
--- a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift
+++ b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift
@@ -24,36 +24,44 @@ public struct MullvadApiRequestFactory: Sendable {
let rawCompletionPointer = Unmanaged.passRetained(completionPointer).toOpaque()
- return switch request {
+ switch request {
case let .getAddressList(retryStrategy):
- MullvadApiCancellable(handle: mullvad_api_get_addresses(
+ return MullvadApiCancellable(handle: mullvad_ios_get_addresses(
apiContext.context,
rawCompletionPointer,
retryStrategy.toRustStrategy()
))
case let .getRelayList(retryStrategy, etag: etag):
- MullvadApiCancellable(handle: mullvad_api_get_relays(
+ return MullvadApiCancellable(handle: mullvad_ios_get_relays(
apiContext.context,
rawCompletionPointer,
retryStrategy.toRustStrategy(),
etag
))
+ case let .sendProblemReport(retryStrategy, problemReportRequest):
+ let rustRequest = RustProblemReportRequest(from: problemReportRequest)
+ return MullvadApiCancellable(handle: mullvad_ios_send_problem_report(
+ apiContext.context,
+ rawCompletionPointer,
+ retryStrategy.toRustStrategy(),
+ rustRequest.toRust()
+ ))
case let .getAccount(retryStrategy, accountNumber: accountNumber):
- MullvadApiCancellable(handle: mullvad_api_get_account(
+ return MullvadApiCancellable(handle: mullvad_ios_get_account(
apiContext.context,
rawCompletionPointer,
retryStrategy.toRustStrategy(),
accountNumber
))
case let .createAccount(retryStrategy):
- MullvadApiCancellable(handle: mullvad_api_create_account(
+ return MullvadApiCancellable(handle: mullvad_ios_create_account(
apiContext.context,
rawCompletionPointer,
retryStrategy.toRustStrategy()
))
case let .deleteAccount(retryStrategy, accountNumber: accountNumber):
- MullvadApiCancellable(handle: mullvad_api_delete_account(
+ return MullvadApiCancellable(handle: mullvad_ios_delete_account(
apiContext.context,
rawCompletionPointer,
retryStrategy.toRustStrategy(),
diff --git a/ios/MullvadREST/Transport/APITransport.swift b/ios/MullvadREST/Transport/APITransport.swift
index 9af1f2779f..d0bf7db48d 100644
--- a/ios/MullvadREST/Transport/APITransport.swift
+++ b/ios/MullvadREST/Transport/APITransport.swift
@@ -34,7 +34,6 @@ public final class APITransport: APITransportProtocol {
let apiRequest = requestFactory.makeRequest(request)
return apiRequest { response in
-
let error: APIError? = if !response.success {
APIError(
statusCode: Int(response.statusCode),
diff --git a/ios/MullvadRustRuntime/RustProblemReportRequest.swift b/ios/MullvadRustRuntime/RustProblemReportRequest.swift
new file mode 100644
index 0000000000..677ced4dfb
--- /dev/null
+++ b/ios/MullvadRustRuntime/RustProblemReportRequest.swift
@@ -0,0 +1,48 @@
+//
+// RustProblemReportRequest.swift
+// MullvadVPN
+//
+// Created by Mojgan on 2025-03-21.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+import MullvadLogging
+import MullvadTypes
+
+final public class RustProblemReportRequest {
+ private let logger = Logger(label: "RustProblemReportRequest")
+ private let addressPointer: UnsafePointer<CChar>?
+ private let messagePointer: UnsafePointer<CChar>?
+ private let logPointer: UnsafePointer<CChar>?
+ private let problemReportMetaData: ProblemReportMetadata
+
+ public init(from request: ProblemReportRequest) {
+ self.problemReportMetaData = swift_problem_report_metadata_new()
+ self.addressPointer = request.address.toCStringPointer()
+ self.messagePointer = request.message.toCStringPointer()
+ self.logPointer = request.log.toCStringPointer()
+
+ for (key, value) in request.metadata {
+ let isAdded = swift_problem_report_metadata_add(problemReportMetaData, key, value)
+ if !isAdded {
+ logger
+ .error("Failed to add metadata. Key: '\(key)' might be invalid or contain unsupported characters.")
+ }
+ }
+ }
+
+ public func toRust() -> SwiftProblemReportRequest {
+ SwiftProblemReportRequest(
+ address: addressPointer,
+ message: messagePointer,
+ log: logPointer,
+ metadata: problemReportMetaData
+ )
+ }
+
+ deinit {
+ swift_problem_report_metadata_free(problemReportMetaData)
+ addressPointer?.deallocate()
+ messagePointer?.deallocate()
+ logPointer?.deallocate()
+ }
+}
diff --git a/ios/MullvadRustRuntime/String+UnsafePointer.swift b/ios/MullvadRustRuntime/String+UnsafePointer.swift
new file mode 100644
index 0000000000..1cb2017c96
--- /dev/null
+++ b/ios/MullvadRustRuntime/String+UnsafePointer.swift
@@ -0,0 +1,27 @@
+//
+// String+UnsafePointer.swift
+// MullvadVPN
+//
+// Created by Mojgan on 2025-04-02.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+extension String {
+ // Ensure the string is converted to a null-terminated C string
+ // UnsafePointer provides no automated memory management or alignment guarantees.
+ // The caller is responsible to manage the memory
+ func toCStringPointer() -> UnsafePointer<CChar>? {
+ // Convert the Swift string to a null-terminated UTF-8 C string
+ guard let cString = cString(using: .utf8) else { return nil }
+
+ // Allocate memory for characters + null terminator
+ let pointer = UnsafeMutablePointer<CChar>.allocate(capacity: cString.count)
+
+ // Copy the characters (including the null terminator)
+ pointer.initialize(from: cString, count: cString.count)
+
+ return UnsafePointer(pointer)
+ }
+}
diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
index 12ce0fd4c0..36774f2d4e 100644
--- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
+++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
@@ -24,6 +24,8 @@ typedef struct EncryptedDnsProxyState EncryptedDnsProxyState;
typedef struct ExchangeCancelToken ExchangeCancelToken;
+typedef struct Map Map;
+
typedef struct RequestCancelHandle RequestCancelHandle;
typedef struct RetryStrategy RetryStrategy;
@@ -54,6 +56,17 @@ typedef struct CompletionCookie {
void *inner;
} CompletionCookie;
+typedef struct ProblemReportMetadata {
+ struct Map *inner;
+} ProblemReportMetadata;
+
+typedef struct SwiftProblemReportRequest {
+ const char *address;
+ const char *message;
+ const char *log;
+ struct ProblemReportMetadata metadata;
+} SwiftProblemReportRequest;
+
typedef struct ProxyHandle {
void *context;
uint16_t port;
@@ -112,7 +125,7 @@ struct SwiftApiContext mullvad_api_init_new(const uint8_t *host,
*
* This function is not safe to call multiple times with the same `CompletionCookie`.
*/
-struct SwiftCancelHandle mullvad_api_get_account(struct SwiftApiContext api_context,
+struct SwiftCancelHandle mullvad_ios_get_account(struct SwiftApiContext api_context,
void *completion_cookie,
struct SwiftRetryStrategy retry_strategy,
const char *account_number);
@@ -129,7 +142,7 @@ struct SwiftCancelHandle mullvad_api_get_account(struct SwiftApiContext api_cont
*
* This function is not safe to call multiple times with the same `CompletionCookie`.
*/
-struct SwiftCancelHandle mullvad_api_create_account(struct SwiftApiContext api_context,
+struct SwiftCancelHandle mullvad_ios_create_account(struct SwiftApiContext api_context,
void *completion_cookie,
struct SwiftRetryStrategy retry_strategy);
@@ -147,7 +160,7 @@ struct SwiftCancelHandle mullvad_api_create_account(struct SwiftApiContext api_c
*
* This function is not safe to call multiple times with the same `CompletionCookie`.
*/
-struct SwiftCancelHandle mullvad_api_delete_account(struct SwiftApiContext api_context,
+struct SwiftCancelHandle mullvad_ios_delete_account(struct SwiftApiContext api_context,
void *completion_cookie,
struct SwiftRetryStrategy retry_strategy,
const char *account_number);
@@ -164,7 +177,7 @@ struct SwiftCancelHandle mullvad_api_delete_account(struct SwiftApiContext api_c
*
* This function is not safe to call multiple times with the same `CompletionCookie`.
*/
-struct SwiftCancelHandle mullvad_api_get_addresses(struct SwiftApiContext api_context,
+struct SwiftCancelHandle mullvad_ios_get_addresses(struct SwiftApiContext api_context,
void *completion_cookie,
struct SwiftRetryStrategy retry_strategy);
@@ -182,7 +195,7 @@ struct SwiftCancelHandle mullvad_api_get_addresses(struct SwiftApiContext api_co
*
* This function is not safe to call multiple times with the same `CompletionCookie`.
*/
-struct SwiftCancelHandle mullvad_api_get_relays(struct SwiftApiContext api_context,
+struct SwiftCancelHandle mullvad_ios_get_relays(struct SwiftApiContext api_context,
void *completion_cookie,
struct SwiftRetryStrategy retry_strategy,
const char *etag);
@@ -225,6 +238,44 @@ extern void mullvad_api_completion_finish(struct SwiftMullvadApiResponse respons
struct CompletionCookie completion_cookie);
/**
+ * Send a problem report 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 string properties of `SwiftProblemReportRequest` must be pointers to a null terminated strings.
+ *
+ * This function is not safe to call multiple times with the same `CompletionCookie`.
+ */
+struct SwiftCancelHandle mullvad_ios_send_problem_report(struct SwiftApiContext api_context,
+ void *completion_cookie,
+ struct SwiftRetryStrategy retry_strategy,
+ struct SwiftProblemReportRequest request);
+
+struct ProblemReportMetadata swift_problem_report_metadata_new(void);
+
+/**
+ * Add key and value pair to the `ProblemReportMetadata`
+ *
+ * # Safety
+ *
+ * `map.inner` must be non-null and point to a valid
+ * - `key` must be a null-terminated UTF-8 string, containing LF-separated machines.
+ * - `value` must be a valid pointer to some valid and aligned pointer-sized memory.
+ */
+bool swift_problem_report_metadata_add(struct ProblemReportMetadata map,
+ const char *key,
+ const char *value);
+
+void swift_problem_report_metadata_free(struct ProblemReportMetadata map);
+
+/**
* Called by the Swift side to signal that the Rust `SwiftMullvadApiResponse` can be safely
* dropped from memory.
*
diff --git a/ios/MullvadTypes/RESTTypes.swift b/ios/MullvadTypes/RESTTypes.swift
index 35354a2424..6ba5c08297 100644
--- a/ios/MullvadTypes/RESTTypes.swift
+++ b/ios/MullvadTypes/RESTTypes.swift
@@ -55,3 +55,17 @@ public struct Device: Codable, Equatable, Sendable {
self.ipv6Address = ipv6Address
}
}
+
+public struct ProblemReportRequest: Codable, Sendable {
+ public let address: String
+ public let message: String
+ public let log: String
+ public let metadata: [String: String]
+
+ public init(address: String, message: String, log: String, metadata: [String: String]) {
+ self.address = address
+ self.message = message
+ self.log = log
+ self.metadata = metadata
+ }
+}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 050bd50e1c..b937691e46 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -1017,6 +1017,8 @@
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 */; };
+ 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, ); }; };
F0ACE3102BE4E478006D5333 /* MullvadMockData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */; };
F0ACE3112BE4E478006D5333 /* MullvadMockData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -1081,6 +1083,7 @@
F0E8E4C52A60499100ED26A3 /* AccountDeletionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E8E4C42A60499100ED26A3 /* AccountDeletionViewController.swift */; };
F0E8E4C92A604E7400ED26A3 /* AccountDeletionInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E8E4C82A604E7400ED26A3 /* AccountDeletionInteractor.swift */; };
F0EF50D52A949F8E0031E8DF /* ChangeLogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0EF50D42A949F8E0031E8DF /* ChangeLogViewModel.swift */; };
+ F0F146942D9462E100BF78E7 /* RustProblemReportRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F146912D94491200BF78E7 /* RustProblemReportRequestTests.swift */; };
F0F316192BF3572B0078DBCF /* RelaySelectorResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */; };
F0F3161B2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */; };
F0F56B092C0E058A009D676B /* ObserverList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CC40EE24A601900019D96E /* ObserverList.swift */; };
@@ -2429,6 +2432,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>"; };
+ 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>"; };
F0ACE32E2BE4EA8B006D5333 /* MockProxyFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProxyFactory.swift; sourceTree = "<group>"; };
@@ -2479,7 +2483,9 @@
F0E8E4C22A602E0D00ED26A3 /* AccountDeletionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletionViewModel.swift; sourceTree = "<group>"; };
F0E8E4C42A60499100ED26A3 /* AccountDeletionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletionViewController.swift; sourceTree = "<group>"; };
F0E8E4C82A604E7400ED26A3 /* AccountDeletionInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletionInteractor.swift; sourceTree = "<group>"; };
+ F0EEFB9E2D8D60E1007FE4B3 /* RustProblemReportRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustProblemReportRequest.swift; sourceTree = "<group>"; };
F0EF50D42A949F8E0031E8DF /* ChangeLogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLogViewModel.swift; sourceTree = "<group>"; };
+ F0F146912D94491200BF78E7 /* RustProblemReportRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustProblemReportRequestTests.swift; sourceTree = "<group>"; };
F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchArguments.swift; sourceTree = "<group>"; };
F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorResult.swift; sourceTree = "<group>"; };
F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoRelaysSatisfyingConstraintsError.swift; sourceTree = "<group>"; };
@@ -2740,6 +2746,7 @@
440E9EF32BDA942E00B1FD11 /* MullvadREST */ = {
isa = PBXGroup;
children = (
+ F0F146932D9462BA00BF78E7 /* MullvadApi */,
7AD63A452CDA665200445268 /* Extensions */,
F072D3D02C071A9100906F64 /* Shadowsocks */,
440E9EF42BDA943B00B1FD11 /* ApiHandlers */,
@@ -4510,17 +4517,19 @@
A992DA1E2C24709F00DE7CE5 /* MullvadRustRuntime */ = {
isa = PBXGroup;
children = (
- A9D9A4D32C36E1EA004088DD /* mullvad_rust_runtime.h */,
- A992DA1F2C24709F00DE7CE5 /* MullvadRustRuntime.h */,
014449942CA293B100C0C2F2 /* EncryptedDNSProxy.swift */,
A948809A2BC9308D0090A44C /* EphemeralPeerExchangeActor.swift */,
A9EB4F9C2B7FAB21002A2D7A /* EphemeralPeerNegotiator.swift */,
A9A557F42B7E3E5C0017ADA8 /* EphemeralPeerReceiver.swift */,
+ A9D9A4D32C36E1EA004088DD /* mullvad_rust_runtime.h */,
7A99D3702D56220E00891FF7 /* MullvadApiCancellable.swift */,
7A3215702D392F0B005DF395 /* MullvadApiCompletion.swift */,
7AB931232D43C2C2005FCEBA /* MullvadApiContext.swift */,
7AB931252D43D222005FCEBA /* MullvadApiResponse.swift */,
+ A992DA1F2C24709F00DE7CE5 /* MullvadRustRuntime.h */,
+ F0EEFB9E2D8D60E1007FE4B3 /* RustProblemReportRequest.swift */,
F0DDE40F2B220458006B57A7 /* ShadowSocksProxy.swift */,
+ F0A89CB62D9D922300580C27 /* String+UnsafePointer.swift */,
584023212A406BF5007B27AC /* TunnelObfuscator.swift */,
);
path = MullvadRustRuntime;
@@ -4807,6 +4816,14 @@
path = ChangeLog;
sourceTree = "<group>";
};
+ F0F146932D9462BA00BF78E7 /* MullvadApi */ = {
+ isa = PBXGroup;
+ children = (
+ F0F146912D94491200BF78E7 /* RustProblemReportRequestTests.swift */,
+ );
+ path = MullvadApi;
+ sourceTree = "<group>";
+ };
F0FA16072D7F03F8007E2546 /* Filter */ = {
isa = PBXGroup;
children = (
@@ -5991,6 +6008,7 @@
F0D5591F2D38051C0072B63F /* LatestChangesNotificationProvider.swift in Sources */,
7A9F28FC2CA69D0C005F2089 /* DAITASettingsTests.swift in Sources */,
A9A5FA2F2ACB05160083449F /* FixedWidthIntegerArithmeticsTests.swift in Sources */,
+ F0F146942D9462E100BF78E7 /* RustProblemReportRequestTests.swift in Sources */,
7AA513862BC91C6B00D081A4 /* LogRotationTests.swift in Sources */,
F04413622BA45CE30018A6EE /* CustomListLocationNodeBuilder.swift in Sources */,
A9A5FA302ACB05160083449F /* InputTextFormatterTests.swift in Sources */,
@@ -6804,8 +6822,10 @@
files = (
A9D9A4B12C36D10E004088DD /* ShadowSocksProxy.swift in Sources */,
014449952CA293B100C0C2F2 /* EncryptedDNSProxy.swift in Sources */,
+ F0A89CB52D9D864B00580C27 /* RustProblemReportRequest.swift in Sources */,
7AB931242D43C2CA005FCEBA /* MullvadApiContext.swift in Sources */,
A9D9A4BB2C36D397004088DD /* EphemeralPeerNegotiator.swift in Sources */,
+ F0A89CB72D9D923300580C27 /* String+UnsafePointer.swift in Sources */,
A9D9A4B22C36D12D004088DD /* TunnelObfuscator.swift in Sources */,
7AB931262D43D22F005FCEBA /* MullvadApiResponse.swift in Sources */,
A9173C322C36CCDD00F6A08C /* EphemeralPeerReceiver.swift in Sources */,
diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift
index 6640473e4a..98746416dc 100644
--- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift
+++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift
@@ -73,20 +73,17 @@ final class ProblemReportInteractor: @unchecked Sendable {
output[entry.key.rawValue] = entry.value
}
- let request = REST.ProblemReportRequest(
+ let request = ProblemReportRequest(
address: email,
message: message,
log: logString,
metadata: metadataDict
)
- _ = self.apiProxy.sendProblemReport(
- request,
- retryStrategy: .default
- ) { result in
+ _ = self.apiProxy.sendProblemReport(request, retryStrategy: .default, completionHandler: { result in
DispatchQueue.main.async {
completion(result)
}
- }
+ })
}
}
diff --git a/ios/MullvadVPNTests/MullvadREST/MullvadApi/RustProblemReportRequestTests.swift b/ios/MullvadVPNTests/MullvadREST/MullvadApi/RustProblemReportRequestTests.swift
new file mode 100644
index 0000000000..aefcfea931
--- /dev/null
+++ b/ios/MullvadVPNTests/MullvadREST/MullvadApi/RustProblemReportRequestTests.swift
@@ -0,0 +1,67 @@
+//
+// RustProblemReportRequestTests.swift
+// MullvadVPN
+//
+// Created by Mojgan on 2025-03-26.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import Testing
+
+@testable import MullvadRustRuntime
+@testable import MullvadTypes
+
+struct RustProblemReportRequestTests {
+ @Test(
+ "Test vaild metadata insertion for SendProblemReport",
+ arguments: [
+ ["key1": "value1"],
+ ["key2": "value2"],
+ ["long_key_abcdefghijklmnopqrstuvwxyz": "long_value_1234567890"],
+ ["special_chars_!@#$%": "special_value_(*&^%)"],
+ ["": ""],
+ ]
+ )
+ func testMetadataInsertion(metadata: [String: String]) {
+ let request = ProblemReportRequest(
+ address: "127.0.0.1",
+ message: "Test message",
+ log: "Log data",
+ metadata: metadata
+ )
+ let rustRequest = RustProblemReportRequest(from: request)
+ let rustStruct = rustRequest.toRust()
+ #expect(rustStruct.metadata != nil, "Metadata should not be for \(metadata)")
+ }
+
+ @Test("Test invalid metadata insertion for SendProblemReport")
+ func testInvalidMetadataHandling() {
+ let invalidMetadata: [[UInt8]: [UInt8]] = [
+ [0xC0, 0x80]: [0xC0, 0x80], // // Incomplete UTF-8 byte sequence for key an value
+ [0x7E]: [0x80], // Valid key , but invalid start byte in UTF-8
+ [0xE0, 0x80]: [0xC2, 0x80], // Malformed UTF-8 multibyte sequence for key and valid value
+ ]
+ let metadata = swift_problem_report_metadata_new()
+ for (keyBytes, valueBytes) in invalidMetadata {
+ keyBytes.withUnsafeBytes { (keyPtr: UnsafeRawBufferPointer) in
+ valueBytes.withUnsafeBytes { (valuePtr: UnsafeRawBufferPointer) in
+ guard let keyBaseAddress = keyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self),
+ let valueBaseAddress = valuePtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
+ return
+ }
+
+ let result = swift_problem_report_metadata_add(
+ metadata,
+ keyBaseAddress,
+ valueBaseAddress
+ )
+
+ #expect(
+ result == false,
+ "Metadata with invalid UTF-8 should not be added. Key/Value: [\(keyBytes): \(valueBytes)]"
+ )
+ }
+ }
+ }
+ }
+}