From ada86c0a955ae0c37dcfafffb9d3f6118115ca0d Mon Sep 17 00:00:00 2001 From: Norbel AMBANUMBEN Date: Thu, 1 Aug 2024 18:54:41 +0100 Subject: [PATCH 1/5] feat: add engine bridge on ios. --- .../kotlin/org/ooni/engine/Engine.kt | 99 ++++++- .../org/ooni/engine/models/TaskSettings.kt | 1 + iosApp/iosApp/engine/IosOonimkallBridge.swift | 254 +++++++++++++++++- 3 files changed, 344 insertions(+), 10 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt index c396ab58..af022ca2 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt @@ -1,8 +1,11 @@ package org.ooni.engine import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.withContext import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.ooni.engine.models.EventResult @@ -25,7 +28,25 @@ class Engine( assetsDir = baseFilePath, ) - val task = bridge.startTask(json.encodeToString(finalSettings)) + val response = httpDo(finalSettings) + println(response) + + val checkinResults = checkIn(finalSettings) + + val task = + bridge.startTask( + json.encodeToString( + checkinResults?.urls?.map { it.url }?.let { + finalSettings.copy( + inputs = it, + options = + finalSettings.options.copy( + maxRuntime = 90, + ), + ) + } ?: finalSettings, + ), + ) while (!task.isDone()) { val eventJson = task.waitForNextEvent() @@ -40,6 +61,82 @@ class Engine( } } + fun session(finalSettings: TaskSettings): OonimkallBridge.Session { + return bridge.newSession( + object : OonimkallBridge.SessionConfig { + override val softwareName: String + get() = finalSettings.options.softwareName + override val softwareVersion: String + get() = finalSettings.options.softwareVersion + override val proxy: String? + get() = null + override val probeServicesURL: String? + get() = "https://api.prod.ooni.io" + override val assetsDir: String + get() = finalSettings.assetsDir.toString() + override val stateDir: String + get() = finalSettings.stateDir.toString() + override val tempDir: String + get() = finalSettings.tempDir.toString() + override val tunnelDir: String + get() = finalSettings.tunnelDir.toString() + override val logger: OonimkallBridge.Logger? + get() = + object : OonimkallBridge.Logger { + override fun debug(msg: String?) { + println("DEBUG: $msg") + } + + override fun info(msg: String?) { + println("INFO: $msg") + } + + override fun warn(msg: String?) { + println("WARN: $msg") + } + } + override val verbose: Boolean + get() = true + }, + ) + } + + suspend fun checkIn(finalSettings: TaskSettings): OonimkallBridge.CheckInResults? { + return withContext(Dispatchers.IO) { + return@withContext session(finalSettings).checkIn( + object : OonimkallBridge.CheckInConfig { + override val charging: Boolean + get() = true + override val onWiFi: Boolean + get() = true + override val platform: String + get() = "android" + override val runType: String + get() = "autorun" + override val softwareName: String + get() = "ooniprobe-android-unattended" + override val softwareVersion: String + get() = "3.8.8" + override val webConnectivityCategories: List + get() = listOf("NEWS") + }, + ) + } + } + + suspend fun httpDo(finalSettings: TaskSettings): String? { + return withContext(Dispatchers.IO) { + return@withContext session(finalSettings).httpDo( + object : OonimkallBridge.HTTPRequest { + override val url: String + get() = "https://api.dev.ooni.io/api/v2/oonirun/links/10426" + override val method: String + get() = "GET" + }, + ).body + } + } + private fun EventResult.toTaskEvent(): TaskEvent? = when (key) { "status.started" -> TaskEvent.Started diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/models/TaskSettings.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/models/TaskSettings.kt index 1711d542..e65c4689 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/models/TaskSettings.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/models/TaskSettings.kt @@ -25,6 +25,7 @@ data class TaskSettings( // built from the flavors + debug or not + -unattended if autorun @SerialName("software_name") val softwareName: String, @SerialName("software_version") val softwareVersion: String, + @SerialName("max_runtime") val maxRuntime: Int? = null, ) @Serializable diff --git a/iosApp/iosApp/engine/IosOonimkallBridge.swift b/iosApp/iosApp/engine/IosOonimkallBridge.swift index 013349ed..3bc11706 100644 --- a/iosApp/iosApp/engine/IosOonimkallBridge.swift +++ b/iosApp/iosApp/engine/IosOonimkallBridge.swift @@ -1,23 +1,259 @@ import composeApp import Oonimkall -class IosOonimkallBridge : OonimkallBridge { +let CONTEXT_TIMEOUT: Int64 = -1 + +class IosOonimkallBridge: OonimkallBridge { + func startTask(settingsSerialized: String) throws -> OonimkallBridgeTask { var error: NSError? let task = OonimkallStartTask(settingsSerialized, &error)! - class Task : OonimkallBridgeTask { + class Task: OonimkallBridgeTask { var task: OonimkallTask - init(task: OonimkallTask) { self.task = task } - func isDone() -> Bool { task.isDone() } - func interrupt() { task.interrupt() } - func waitForNextEvent() -> String { task.waitForNextEvent() } + + init(task: OonimkallTask) { + self.task = task + } + + func isDone() -> Bool { + task.isDone() + } + + func interrupt() { + task.interrupt() + } + + func waitForNextEvent() -> String { + task.waitForNextEvent() + } } - + return Task(task: task) } - + func doNewSession(sessionConfig: OonimkallBridgeSessionConfig) throws -> OonimkallBridgeSession { - fatalError("Not Implemented") + class IosSession: OonimkallBridgeSession { + private let sessionConfig: OonimkallSessionConfig + + init(sessionConfig: OonimkallSessionConfig) { + self.sessionConfig = sessionConfig + } + + func checkIn(config: OonimkallBridgeCheckInConfig) throws -> any OonimkallBridgeCheckInResults { + var error: NSError? + let ses = OonimkallNewSession(sessionConfig, &error) + // throw error if any + if error != nil { + throw error! + } + guard let context = ses?.newContext(withTimeout: CONTEXT_TIMEOUT) else { + throw NSError() + } + do { + let info = try ses?.check(in: context, config: config.toMk()) + return IosCheckInResults(info: info!) + } catch { + throw error + } + } + + func httpDo(request: OonimkallBridgeHTTPRequest) throws -> OonimkallBridgeHTTPResponse { + var error: NSError? + let ses = OonimkallNewSession(sessionConfig, &error) + // throw error if any + if error != nil { + throw error! + } + guard let context = ses?.newContext(withTimeout: CONTEXT_TIMEOUT) else { + throw NSError() + } + do { + let response = try ses?.httpDo(context, jreq: request.toMk()) + return IosHTTPResponse(response: response) + } catch { + throw error + } + } + + func submitMeasurement(measurement: String) throws -> any OonimkallBridgeSubmitMeasurementResults { + var error: NSError? + let ses = OonimkallNewSession(sessionConfig, &error) + // throw error if any + if error != nil { + throw error! + } + guard let context = ses?.newContext(withTimeout: CONTEXT_TIMEOUT) else { + throw NSError() + } + do { + + let result: OonimkallSubmitMeasurementResults? = try ses?.submit(context, measurement: measurement) + return IosSubmitMeasurementResults(results: result) + } catch { + throw error + } + } + } + + return IosSession(sessionConfig: sessionConfig.toMk()) + } +} + + +extension OonimkallBridgeSessionConfig { + func toMk() -> OonimkallSessionConfig { + + let config: OonimkallSessionConfig = OonimkallSessionConfig() + config.softwareName = softwareName + config.softwareVersion = softwareVersion + config.assetsDir = assetsDir + config.stateDir = stateDir + config.tempDir = tempDir + config.tunnelDir = tunnelDir + if let probeServicesURL = probeServicesURL { + config.probeServicesURL = probeServicesURL + } + if let proxy = proxy { + config.proxy = proxy + } + // Problem setting logger + if let logger = logger { + let applicationLogger = IosLogger(logger: logger) + // config.logger = applicationLogger + } + config.verbose = verbose + return config + } +} + +extension OonimkallBridgeCheckInConfig { + func toMk() -> OonimkallCheckInConfig { + let config = OonimkallCheckInConfig() + config.charging = charging + config.onWiFi = onWiFi + config.platform = platform + config.runType = runType + config.softwareName = softwareName + config.softwareVersion = softwareVersion + config.webConnectivity = OonimkallCheckInConfigWebConnectivity() + webConnectivityCategories.forEach { category in + config.webConnectivity?.addCategory(category) + } + return config + } +} + +extension OonimkallBridgeHTTPRequest { + func toMk() -> OonimkallHTTPRequest { + let request = OonimkallHTTPRequest() + request.method = method + request.url = url + return request + } +} + +class IosHTTPResponse: OonimkallBridgeHTTPResponse { + private let response: OonimkallHTTPResponse? + + init(response: OonimkallHTTPResponse?) { + self.response = response + } + + var body: String? { + response?.body + } +} + +class IosSubmitMeasurementResults: OonimkallBridgeSubmitMeasurementResults { + private let results: OonimkallSubmitMeasurementResults? + + init(results: OonimkallSubmitMeasurementResults?) { + self.results = results + } + + var updatedMeasurement: String? { + results?.updatedMeasurement + } + + var updatedReportId: String? { + results?.updatedReportID + } +} + +@objc +class IosLogger: OonimkallLogger { + private let logger: OonimkallBridgeLogger? + + override init(ref: Any) { + self.logger = 0 as? any OonimkallBridgeLogger + super.init(ref: ref) + } + + init(logger: OonimkallBridgeLogger) { + self.logger = logger + super.init() + } + + override func debug(_ msg: String?) { + logger?.debug(msg: msg) + } + + override func info(_ msg: String?) { + logger?.info(msg: msg) + } + + override func warn(_ msg: String?) { + logger?.warn(msg: msg) + } +} + + +class IosCheckInResults: OonimkallBridgeCheckInResults { + private let info: OonimkallCheckInInfo + + init(info: OonimkallCheckInInfo) { + self.info = info + } + + var reportId: String? { + info.webConnectivity?.reportID + } + + var urls: [OonimkallBridgeUrlInfo] { + + var responseUrls = [OonimkallBridgeUrlInfo]() + + let size = info.webConnectivity?.size() ?? 0 + + for i in 0.. Date: Mon, 5 Aug 2024 12:12:46 +0100 Subject: [PATCH 2/5] feat: add proper error descriptions --- iosApp/iosApp/engine/IosOonimkallBridge.swift | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/iosApp/iosApp/engine/IosOonimkallBridge.swift b/iosApp/iosApp/engine/IosOonimkallBridge.swift index 3bc11706..70650d51 100644 --- a/iosApp/iosApp/engine/IosOonimkallBridge.swift +++ b/iosApp/iosApp/engine/IosOonimkallBridge.swift @@ -40,6 +40,22 @@ class IosOonimkallBridge: OonimkallBridge { self.sessionConfig = sessionConfig } + func error(_ message: String, code: Int = 0, domain: String = "IosOonimkallBridge", function: String = #function, file: String = #file, line: Int = #line) -> NSError { + + let functionKey = "\(domain).function" + let fileKey = "\(domain).file" + let lineKey = "\(domain).line" + + let error = NSError(domain: domain, code: code, userInfo: [ + message: message, + functionKey: function, + fileKey: file, + lineKey: line + ]) + + return error + } + func checkIn(config: OonimkallBridgeCheckInConfig) throws -> any OonimkallBridgeCheckInResults { var error: NSError? let ses = OonimkallNewSession(sessionConfig, &error) @@ -48,7 +64,7 @@ class IosOonimkallBridge: OonimkallBridge { throw error! } guard let context = ses?.newContext(withTimeout: CONTEXT_TIMEOUT) else { - throw NSError() + throw self.error("Unable to create context") } do { let info = try ses?.check(in: context, config: config.toMk()) @@ -66,7 +82,7 @@ class IosOonimkallBridge: OonimkallBridge { throw error! } guard let context = ses?.newContext(withTimeout: CONTEXT_TIMEOUT) else { - throw NSError() + throw self.error("Unable to create context") } do { let response = try ses?.httpDo(context, jreq: request.toMk()) @@ -84,7 +100,7 @@ class IosOonimkallBridge: OonimkallBridge { throw error! } guard let context = ses?.newContext(withTimeout: CONTEXT_TIMEOUT) else { - throw NSError() + throw self.error("Unable to create context") } do { From 4c31cd8ff14c3f54e119447d0874215f24f006b2 Mon Sep 17 00:00:00 2001 From: Norbel AMBANUMBEN Date: Mon, 5 Aug 2024 12:47:42 +0100 Subject: [PATCH 3/5] merge with main --- .../kotlin/org/ooni/engine/Engine.kt | 40 +++--- iosApp/iosApp/engine/IosOonimkallBridge.swift | 114 +++++------------- 2 files changed, 48 insertions(+), 106 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt index af022ca2..1624f2c7 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt @@ -1,5 +1,6 @@ package org.ooni.engine +import co.touchlab.kermit.Logger import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO @@ -29,7 +30,9 @@ class Engine( ) val response = httpDo(finalSettings) - println(response) + response?.let { + Logger.d(it) + } val checkinResults = checkIn(finalSettings) @@ -104,22 +107,15 @@ class Engine( suspend fun checkIn(finalSettings: TaskSettings): OonimkallBridge.CheckInResults? { return withContext(Dispatchers.IO) { return@withContext session(finalSettings).checkIn( - object : OonimkallBridge.CheckInConfig { - override val charging: Boolean - get() = true - override val onWiFi: Boolean - get() = true - override val platform: String - get() = "android" - override val runType: String - get() = "autorun" - override val softwareName: String - get() = "ooniprobe-android-unattended" - override val softwareVersion: String - get() = "3.8.8" - override val webConnectivityCategories: List - get() = listOf("NEWS") - }, + OonimkallBridge.CheckInConfig( + charging = true, + onWiFi = true, + platform = "android", + runType = "autorun", + softwareName = "ooniprobe-android-unattended", + softwareVersion = "3.8.8", + webConnectivityCategories = listOf("NEWS"), + ), ) } } @@ -127,12 +123,10 @@ class Engine( suspend fun httpDo(finalSettings: TaskSettings): String? { return withContext(Dispatchers.IO) { return@withContext session(finalSettings).httpDo( - object : OonimkallBridge.HTTPRequest { - override val url: String - get() = "https://api.dev.ooni.io/api/v2/oonirun/links/10426" - override val method: String - get() = "GET" - }, + OonimkallBridge.HTTPRequest( + url = "https://api.dev.ooni.io/api/v2/oonirun/links/10426", + method = "GET", + ) ).body } } diff --git a/iosApp/iosApp/engine/IosOonimkallBridge.swift b/iosApp/iosApp/engine/IosOonimkallBridge.swift index 70650d51..92615b76 100644 --- a/iosApp/iosApp/engine/IosOonimkallBridge.swift +++ b/iosApp/iosApp/engine/IosOonimkallBridge.swift @@ -56,7 +56,7 @@ class IosOonimkallBridge: OonimkallBridge { return error } - func checkIn(config: OonimkallBridgeCheckInConfig) throws -> any OonimkallBridgeCheckInResults { + func checkIn(config: OonimkallBridgeCheckInConfig) throws -> OonimkallBridgeCheckInResults { var error: NSError? let ses = OonimkallNewSession(sessionConfig, &error) // throw error if any @@ -68,7 +68,30 @@ class IosOonimkallBridge: OonimkallBridge { } do { let info = try ses?.check(in: context, config: config.toMk()) - return IosCheckInResults(info: info!) + + + var responseUrls = [OonimkallBridgeUrlInfo]() + + let size = info?.webConnectivity?.size() ?? 0 + + for i in 0.. any OonimkallBridgeSubmitMeasurementResults { + func submitMeasurement(measurement: String) throws -> OonimkallBridgeSubmitMeasurementResults { var error: NSError? let ses = OonimkallNewSession(sessionConfig, &error) // throw error if any @@ -105,7 +128,10 @@ class IosOonimkallBridge: OonimkallBridge { do { let result: OonimkallSubmitMeasurementResults? = try ses?.submit(context, measurement: measurement) - return IosSubmitMeasurementResults(results: result) + return OonimkallBridgeSubmitMeasurementResults( + updatedMeasurement: result?.updatedMeasurement, + updatedReportId: result?.updatedReportID + ) } catch { throw error } @@ -169,34 +195,6 @@ extension OonimkallBridgeHTTPRequest { } } -class IosHTTPResponse: OonimkallBridgeHTTPResponse { - private let response: OonimkallHTTPResponse? - - init(response: OonimkallHTTPResponse?) { - self.response = response - } - - var body: String? { - response?.body - } -} - -class IosSubmitMeasurementResults: OonimkallBridgeSubmitMeasurementResults { - private let results: OonimkallSubmitMeasurementResults? - - init(results: OonimkallSubmitMeasurementResults?) { - self.results = results - } - - var updatedMeasurement: String? { - results?.updatedMeasurement - } - - var updatedReportId: String? { - results?.updatedReportID - } -} - @objc class IosLogger: OonimkallLogger { private let logger: OonimkallBridgeLogger? @@ -223,53 +221,3 @@ class IosLogger: OonimkallLogger { logger?.warn(msg: msg) } } - - -class IosCheckInResults: OonimkallBridgeCheckInResults { - private let info: OonimkallCheckInInfo - - init(info: OonimkallCheckInInfo) { - self.info = info - } - - var reportId: String? { - info.webConnectivity?.reportID - } - - var urls: [OonimkallBridgeUrlInfo] { - - var responseUrls = [OonimkallBridgeUrlInfo]() - - let size = info.webConnectivity?.size() ?? 0 - - for i in 0.. Date: Mon, 5 Aug 2024 12:55:59 +0100 Subject: [PATCH 4/5] chore: nicely format kotlin --- composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt index 1624f2c7..db1e5741 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt @@ -126,7 +126,7 @@ class Engine( OonimkallBridge.HTTPRequest( url = "https://api.dev.ooni.io/api/v2/oonirun/links/10426", method = "GET", - ) + ), ).body } } From a36fba3705502c34b83bb4c06d722e5b2f9a7b1e Mon Sep 17 00:00:00 2001 From: Norbel AMBANUMBEN Date: Mon, 5 Aug 2024 13:47:44 +0100 Subject: [PATCH 5/5] chore: change type of SessionCongig --- .../kotlin/org/ooni/engine/Engine.kt | 58 ++++++++----------- .../kotlin/org/ooni/engine/OonimkallBridge.kt | 27 ++++----- 2 files changed, 36 insertions(+), 49 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt index db1e5741..08fb4e2f 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt @@ -66,41 +66,31 @@ class Engine( fun session(finalSettings: TaskSettings): OonimkallBridge.Session { return bridge.newSession( - object : OonimkallBridge.SessionConfig { - override val softwareName: String - get() = finalSettings.options.softwareName - override val softwareVersion: String - get() = finalSettings.options.softwareVersion - override val proxy: String? - get() = null - override val probeServicesURL: String? - get() = "https://api.prod.ooni.io" - override val assetsDir: String - get() = finalSettings.assetsDir.toString() - override val stateDir: String - get() = finalSettings.stateDir.toString() - override val tempDir: String - get() = finalSettings.tempDir.toString() - override val tunnelDir: String - get() = finalSettings.tunnelDir.toString() - override val logger: OonimkallBridge.Logger? - get() = - object : OonimkallBridge.Logger { - override fun debug(msg: String?) { - println("DEBUG: $msg") - } - - override fun info(msg: String?) { - println("INFO: $msg") - } - - override fun warn(msg: String?) { - println("WARN: $msg") - } + OonimkallBridge.SessionConfig( + softwareName = finalSettings.options.softwareName, + softwareVersion = finalSettings.options.softwareVersion, + proxy = null, + probeServicesURL = "https://api.prod.ooni.io", + assetsDir = finalSettings.assetsDir.toString(), + stateDir = finalSettings.stateDir.toString(), + tempDir = finalSettings.tempDir.toString(), + tunnelDir = finalSettings.tunnelDir.toString(), + logger = + object : OonimkallBridge.Logger { + override fun debug(msg: String?) { + msg?.let { Logger.d(it) } } - override val verbose: Boolean - get() = true - }, + + override fun info(msg: String?) { + msg?.let { Logger.d(it) } + } + + override fun warn(msg: String?) { + msg?.let { Logger.d(it) } + } + }, + verbose = true, + ), ) } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt index f7ee3244..2ebbb887 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt @@ -23,21 +23,18 @@ interface OonimkallBridge { fun warn(msg: String?) } - interface SessionConfig { - val softwareName: String - val softwareVersion: String - - val proxy: String? - val probeServicesURL: String? - - val assetsDir: String - val stateDir: String - val tempDir: String - val tunnelDir: String - - val logger: Logger? - val verbose: Boolean - } + data class SessionConfig( + val softwareName: String, + val softwareVersion: String, + val proxy: String?, + val probeServicesURL: String?, + val assetsDir: String, + val stateDir: String, + val tempDir: String, + val tunnelDir: String, + val logger: Logger?, + val verbose: Boolean, + ) interface Session { @Throws(Exception::class)