Skip to content

Commit

Permalink
Merge pull request #31 from ooni/feat/engine-bridge-ios
Browse files Browse the repository at this point in the history
feat: add engine bridge on ios.
  • Loading branch information
sdsantos authored Aug 5, 2024
2 parents 092afc7 + a36fba3 commit 7fc4492
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 25 deletions.
83 changes: 82 additions & 1 deletion composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
Expand All @@ -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
Expand Down
27 changes: 12 additions & 15 deletions composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
218 changes: 209 additions & 9 deletions iosApp/iosApp/engine/IosOonimkallBridge.swift
Original file line number Diff line number Diff line change
@@ -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..<size {

if info?.webConnectivity?.at(i) != nil {
let urlInfo: OonimkallURLInfo = (info?.webConnectivity?.at(i))!
responseUrls.append(
OonimkallBridgeUrlInfo(
url: urlInfo.url,
categoryCode: urlInfo.categoryCode,
countryCode: urlInfo.countryCode
)
)
}
}

return OonimkallBridgeCheckInResults(
reportId: info?.webConnectivity?.reportID,
urls: responseUrls
)
} 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 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)
}
}

0 comments on commit 7fc4492

Please sign in to comment.