diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt index c396ab58..08fb4e2f 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt @@ -1,8 +1,12 @@ package org.ooni.engine +import co.touchlab.kermit.Logger 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 +29,27 @@ class Engine( assetsDir = baseFilePath, ) - val task = bridge.startTask(json.encodeToString(finalSettings)) + val response = httpDo(finalSettings) + response?.let { + Logger.d(it) + } + + 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 +64,63 @@ class Engine( } } + fun session(finalSettings: TaskSettings): OonimkallBridge.Session { + return bridge.newSession( + 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 fun info(msg: String?) { + msg?.let { Logger.d(it) } + } + + override fun warn(msg: String?) { + msg?.let { Logger.d(it) } + } + }, + verbose = true, + ), + ) + } + + suspend fun checkIn(finalSettings: TaskSettings): OonimkallBridge.CheckInResults? { + return withContext(Dispatchers.IO) { + return@withContext session(finalSettings).checkIn( + OonimkallBridge.CheckInConfig( + charging = true, + onWiFi = true, + platform = "android", + runType = "autorun", + softwareName = "ooniprobe-android-unattended", + softwareVersion = "3.8.8", + webConnectivityCategories = listOf("NEWS"), + ), + ) + } + } + + suspend fun httpDo(finalSettings: TaskSettings): String? { + return withContext(Dispatchers.IO) { + return@withContext session(finalSettings).httpDo( + OonimkallBridge.HTTPRequest( + url = "https://api.dev.ooni.io/api/v2/oonirun/links/10426", + method = "GET", + ), + ).body + } + } + private fun EventResult.toTaskEvent(): TaskEvent? = when (key) { "status.started" -> TaskEvent.Started 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) 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 4d1d9ad6..b84cb982 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/models/TaskSettings.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/models/TaskSettings.kt @@ -27,6 +27,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..92615b76 100644 --- a/iosApp/iosApp/engine/IosOonimkallBridge.swift +++ b/iosApp/iosApp/engine/IosOonimkallBridge.swift @@ -1,23 +1,223 @@ 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 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 -> 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 self.error("Unable to create context") + } + do { + let info = try ses?.check(in: context, config: config.toMk()) + + + var responseUrls = [OonimkallBridgeUrlInfo]() + + let size = info?.webConnectivity?.size() ?? 0 + + for i in 0.. 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 self.error("Unable to create context") + } + do { + let response = try ses?.httpDo(context, jreq: request.toMk()) + return OonimkallBridgeHTTPResponse(body: response?.body) + } catch { + throw error + } + } + + func submitMeasurement(measurement: String) throws -> 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 self.error("Unable to create context") + } + do { + + let result: OonimkallSubmitMeasurementResults? = try ses?.submit(context, measurement: measurement) + return OonimkallBridgeSubmitMeasurementResults( + updatedMeasurement: result?.updatedMeasurement, + updatedReportId: result?.updatedReportID + ) + } 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 + } +} + +@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) } }