From f8aeeb42fa5cc7f1cc428bb4b34cb8d5de09bb51 Mon Sep 17 00:00:00 2001 From: mojganii Date: Thu, 20 Mar 2025 15:54:50 +0100 Subject: Call sendProblemReport from Rust in Swift --- ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift | 14 ++++++++++ .../Extensions/String+UnsafePointer.swift | 16 ++++++++++++ .../MullvadAPI/APIRequest/APIRequest.swift | 5 ++++ .../MullvadAPI/MullvadApiRequestFactory.swift | 28 ++++++++++++++++++++ ios/MullvadREST/Transport/APITransport.swift | 1 - ios/MullvadVPN.xcodeproj/project.pbxproj | 4 +++ .../ProblemReport/ProblemReportInteractor.swift | 7 ++--- mullvad-api/src/lib.rs | 2 +- mullvad-ios/src/api_client/mod.rs | 2 +- mullvad-ios/src/api_client/response.rs | 1 - mullvad-ios/src/api_client/send_problem_report.rs | 30 ++++++++++++++++------ 11 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 ios/MullvadREST/Extensions/String+UnsafePointer.swift diff --git a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift index d45745446e..39585a88b7 100644 --- a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift +++ b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift @@ -263,6 +263,20 @@ extension REST { let newExpiry: Date } + 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 + } + } + private struct SubmitVoucherRequest: Encodable, Sendable { let voucherCode: String } diff --git a/ios/MullvadREST/Extensions/String+UnsafePointer.swift b/ios/MullvadREST/Extensions/String+UnsafePointer.swift new file mode 100644 index 0000000000..4d60a1c77e --- /dev/null +++ b/ios/MullvadREST/Extensions/String+UnsafePointer.swift @@ -0,0 +1,16 @@ +// +// String+UnsafePointer.swift +// MullvadVPN +// +// Created by Mojgan on 2025-03-19. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// +import Foundation +extension String { + func toUnsafePointer() -> UnsafePointer? { + guard let data = self.data(using: .utf8) else { return nil } + let buffer = UnsafeMutablePointer.allocate(capacity: data.count) + data.copyBytes(to: buffer, count: data.count) + return UnsafePointer(buffer) + } +} diff --git a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift index 68e7e41663..7810230464 100644 --- a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift +++ b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift @@ -9,6 +9,8 @@ public enum APIRequest: Codable, Sendable { case getAddressList(_ retryStrategy: REST.RetryStrategy) case getRelayList(_ retryStrategy: REST.RetryStrategy, etag: String?) + case sendProblemReport(_ retryStrategy: REST.RetryStrategy, problemReportRequest: REST.ProblemReportRequest) + case createAccount(_ retryStrategy: REST.RetryStrategy) case getAccount(_ retryStrategy: REST.RetryStrategy, accountNumber: String) case deleteAccount(_ retryStrategy: REST.RetryStrategy, accountNumber: String) @@ -19,6 +21,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 +37,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..970f70ef76 100644 --- a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift +++ b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift @@ -39,6 +39,13 @@ public struct MullvadApiRequestFactory: Sendable { retryStrategy.toRustStrategy(), etag )) + case let .sendProblemReport(retryStrategy, problemReportRequest): + MullvadApiCancellable(handle: mullvad_api_send_problem_report( + apiContext.context, + rawCompletionPointer, + retryStrategy.toRustStrategy(), + problemReportRequest.toRust() + )) case let .getAccount(retryStrategy, accountNumber: accountNumber): MullvadApiCancellable(handle: mullvad_api_get_account( apiContext.context, @@ -67,3 +74,24 @@ public struct MullvadApiRequestFactory: Sendable { extension REST { public typealias MullvadApiRequestHandler = (((MullvadApiResponse) throws -> Void)?) -> MullvadApiCancellable } + +private extension REST.ProblemReportRequest { + func toRust() -> UnsafePointer { + let structPointer = UnsafeMutablePointer.allocate(capacity: 1) + + let addressPointer = address.toUnsafePointer() + let messagePointer = message.toUnsafePointer() + let logPointer = log.toUnsafePointer() + + structPointer.initialize(to: SwiftProblemReportRequest( + address: addressPointer, + address_len: UInt(address.utf8.count), + message: messagePointer, + message_len: UInt(message.utf8.count), + log: logPointer, + log_len: UInt(log.utf8.count) + )) + + return UnsafePointer(structPointer) + } +} 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/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 050bd50e1c..8c64f72340 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -977,6 +977,7 @@ F062000A2CB7EB42002E6DB9 /* CGSize+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06200092CB7EB42002E6DB9 /* CGSize+Helpers.swift */; }; F062000C2CB7EB5D002E6DB9 /* UIImage+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F062000B2CB7EB5D002E6DB9 /* UIImage+Helpers.swift */; }; F062B94D2C16E09700B6D47A /* TunnelSettingsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */; }; + F0647C322D8AF1E800D2C489 /* String+UnsafePointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0647C312D8AF1CD00D2C489 /* String+UnsafePointer.swift */; }; F072D3CF2C07122400906F64 /* SettingsUpdaterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3CE2C07122400906F64 /* SettingsUpdaterTests.swift */; }; F072D3D22C071AD100906F64 /* ShadowsocksLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */; }; F073FCB32C6617D70062EA1D /* TunnelStore+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F073FCB22C6617D70062EA1D /* TunnelStore+Stubs.swift */; }; @@ -2404,6 +2405,7 @@ F06200092CB7EB42002E6DB9 /* CGSize+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGSize+Helpers.swift"; sourceTree = ""; }; F062000B2CB7EB5D002E6DB9 /* UIImage+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Helpers.swift"; sourceTree = ""; }; F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsManagerTests.swift; sourceTree = ""; }; + F0647C312D8AF1CD00D2C489 /* String+UnsafePointer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+UnsafePointer.swift"; sourceTree = ""; }; F072D3CE2C07122400906F64 /* SettingsUpdaterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUpdaterTests.swift; sourceTree = ""; }; F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoaderTests.swift; sourceTree = ""; }; F073FCB22C6617D70062EA1D /* TunnelStore+Stubs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TunnelStore+Stubs.swift"; sourceTree = ""; }; @@ -4325,6 +4327,7 @@ 7AD63A422CDA661B00445268 /* Extensions */ = { isa = PBXGroup; children = ( + F0647C312D8AF1CD00D2C489 /* String+UnsafePointer.swift */, 7AD63A432CDA662900445268 /* UInt+Counting.swift */, ); path = Extensions; @@ -5812,6 +5815,7 @@ F06045EA2B23217E00B2D37A /* ShadowsocksTransport.swift in Sources */, 06799AFC28F98EE300ACD94E /* AddressCache.swift in Sources */, 7ADCB2D82B6A6EB300C88F89 /* AnyRelay.swift in Sources */, + F0647C322D8AF1E800D2C489 /* String+UnsafePointer.swift in Sources */, 06799AF028F98E4800ACD94E /* REST.swift in Sources */, 06799ADF28F98E4800ACD94E /* RESTDevicesProxy.swift in Sources */, 06799ADA28F98E4800ACD94E /* RESTResponseHandler.swift in Sources */, diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift index 6640473e4a..55fc1df7d3 100644 --- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift +++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift @@ -80,13 +80,10 @@ final class ProblemReportInteractor: @unchecked Sendable { metadata: metadataDict ) - _ = self.apiProxy.sendProblemReport( - request, - retryStrategy: .default - ) { result in + _ = self.apiProxy.mullvadSendProblemReport(request, retryStrategy: .default, completionHandler: { result in DispatchQueue.main.async { completion(result) } - } + }) } } diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs index 987098c591..eb3589ac67 100644 --- a/mullvad-api/src/lib.rs +++ b/mullvad-api/src/lib.rs @@ -687,7 +687,7 @@ impl ProblemReportProxy { log: &str, metadata: &BTreeMap, ) -> impl Future> { - let future = self.porblem_report_response(email, message, log, metadata); + let future = self.porblem_report_response(email, message, log, metadata); async move { future.await?; diff --git a/mullvad-ios/src/api_client/mod.rs b/mullvad-ios/src/api_client/mod.rs index 234724530e..1018ef4b95 100644 --- a/mullvad-ios/src/api_client/mod.rs +++ b/mullvad-ios/src/api_client/mod.rs @@ -15,7 +15,7 @@ mod cancellation; mod completion; mod response; mod retry_strategy; -mod send_problem_report; +mod send_problem_report; #[repr(C)] pub struct SwiftApiContext(*const ApiContext); diff --git a/mullvad-ios/src/api_client/response.rs b/mullvad-ios/src/api_client/response.rs index 90f1552ffd..3b017e37c6 100644 --- a/mullvad-ios/src/api_client/response.rs +++ b/mullvad-ios/src/api_client/response.rs @@ -8,7 +8,6 @@ use mullvad_api::{ RelayListProxy, StatusCode, }; - #[repr(C)] pub struct SwiftMullvadApiResponse { body: *mut u8, diff --git a/mullvad-ios/src/api_client/send_problem_report.rs b/mullvad-ios/src/api_client/send_problem_report.rs index f96f8926d2..1d2547298f 100644 --- a/mullvad-ios/src/api_client/send_problem_report.rs +++ b/mullvad-ios/src/api_client/send_problem_report.rs @@ -13,7 +13,7 @@ use super::{ }; use mullvad_api::rest::Error; -use std::{collections::BTreeMap}; +use std::collections::BTreeMap; use std::slice; use tokio::task::JoinHandle; @@ -35,10 +35,15 @@ pub unsafe extern "C" fn mullvad_api_send_problem_report( let api_context = api_context.into_rust_context(); let retry_strategy = unsafe { retry_strategy.into_rust() }; - let problem_report_request = match unsafe { ProblemReportRequest::from_swift_parameters(request) } { + let problem_report_request = match unsafe { + ProblemReportRequest::from_swift_parameters(request) + } { Some(req) => req, None => { - let err = Error::ApiError(rest::StatusCode::BAD_REQUEST, "Failed to send problem report: invalid address, message, or log data.".to_string()); + let err = Error::ApiError( + rest::StatusCode::BAD_REQUEST, + "Failed to send problem report: invalid address, message, or log data.".to_string(), + ); log::error!("{err:?}"); completion.finish(SwiftMullvadApiResponse::rest_error(err)); return SwiftCancelHandle::empty(); @@ -72,7 +77,14 @@ async fn mullvad_api_send_problem_report_inner( let api = ProblemReportProxy::new(rest_client); let empty_metadata: BTreeMap = BTreeMap::new(); - let future_factory = || api.porblem_report_response(&problem_report_request.address, &problem_report_request.message, &(String::from_utf8_lossy(&problem_report_request.log)), &empty_metadata); + let future_factory = || { + api.porblem_report_response( + &problem_report_request.address, + &problem_report_request.message, + &(String::from_utf8_lossy(&problem_report_request.log)), + &empty_metadata, + ) + }; let should_retry = |result: &Result<_, rest::Error>| match result { Err(err) => err.is_network_error(), @@ -81,7 +93,6 @@ async fn mullvad_api_send_problem_report_inner( let response = retry_future(future_factory, should_retry, retry_strategy.delays()).await?; SwiftMullvadApiResponse::with_body(response).await - } #[repr(C)] @@ -100,7 +111,6 @@ struct ProblemReportRequest { log: Vec, } - unsafe impl Send for SwiftProblemReportRequest {} impl ProblemReportRequest { @@ -119,6 +129,10 @@ impl ProblemReportRequest { let message = String::from_utf8(message_slice.to_vec()).ok()?; let log = log_slice.to_vec(); - Some(Self { address, message, log }) + Some(Self { + address, + message, + log, + }) } -} \ No newline at end of file +} -- cgit v1.3-3-g829e