Skip to content

Commit

Permalink
Finish task events
Browse files Browse the repository at this point in the history
  • Loading branch information
sdsantos committed Aug 5, 2024
1 parent 0194c32 commit 9598837
Show file tree
Hide file tree
Showing 12 changed files with 498 additions and 68 deletions.
2 changes: 1 addition & 1 deletion composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ kotlin {
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.kotlin.serialization)
implementation(libs.bundles.kotlin)
implementation(libs.bundles.ui)
implementation(libs.bundles.tooling)

Expand Down
44 changes: 5 additions & 39 deletions composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.ooni.engine.models.EventResult
import org.ooni.engine.models.TaskEvent
import org.ooni.engine.models.TaskEventResult
import org.ooni.engine.models.TaskSettings
import kotlin.math.roundToInt

class Engine(
private val bridge: OonimkallBridge,
private val json: Json,
private val baseFilePath: String,
private val cacheDir: String,
private val taskEventMapper: TaskEventMapper,
) {
fun startTask(taskSettings: TaskSettings): Flow<TaskEvent> =
channelFlow {
Expand All @@ -42,7 +42,7 @@ class Engine(
json.encodeToString(
checkinResults?.urls?.map { it.url }?.let {
finalSettings.copy(
inputs = it,
inputs = it.take(1),
options =
finalSettings.options.copy(
maxRuntime = 90,
Expand All @@ -54,8 +54,8 @@ class Engine(

while (!task.isDone()) {
val eventJson = task.waitForNextEvent()
val eventResult = json.decodeFromString<EventResult>(eventJson)
eventResult.toTaskEvent()?.let { send(it) }
val taskEventResult = json.decodeFromString<TaskEventResult>(eventJson)
taskEventMapper(taskEventResult)?.let { send(it) }
}

invokeOnClose {
Expand Down Expand Up @@ -121,38 +121,4 @@ class Engine(
).body
}
}

private fun EventResult.toTaskEvent(): TaskEvent? =
when (key) {
"status.started" -> TaskEvent.Started

"status.end" -> TaskEvent.StatusEnd

"status.progress" ->
value?.percentage?.let { percentageValue ->
TaskEvent.Progress(
percentage = (percentageValue * 100.0).roundToInt(),
message = value?.message,
)
}

"log" ->
value?.message?.let { message ->
TaskEvent.Log(
level = value?.logLevel,
message = message,
)
}

"status.report_create" ->
value?.reportId?.let {
TaskEvent.ReportCreate(reportId = it)
}

"task_terminated" -> TaskEvent.TaskTerminated

"failure.startup" -> TaskEvent.FailureStartup(message = value?.failure)

else -> null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.ooni.engine

import org.ooni.engine.models.NetworkType

fun interface NetworkTypeFinder {
operator fun invoke(): NetworkType
}
122 changes: 122 additions & 0 deletions composeApp/src/commonMain/kotlin/org/ooni/engine/TaskEventMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package org.ooni.engine

import co.touchlab.kermit.Logger
import kotlinx.serialization.json.Json
import org.ooni.engine.models.TaskEvent
import org.ooni.engine.models.TaskEventResult
import kotlin.math.roundToInt

class TaskEventMapper(
private val networkTypeFinder: NetworkTypeFinder,
private val json: Json,
) {
operator fun invoke(result: TaskEventResult): TaskEvent? {
val key = result.key
val value = result.value

return when (key) {
"bug.json_dump" ->
value?.let {
TaskEvent.BugJsonDump(value = value)
} ?: run {
Logger.d("Task Event $key missing 'value'")
null
}

"failure.measurement_submission" ->
TaskEvent.MeasurementSubmissionFailure(
index = value?.idx ?: 0,
message = value?.failure,
)

"failure.resolver_lookup" -> TaskEvent.ResolverLookupFailure(message = value?.failure)

"failure.startup" -> TaskEvent.StartupFailure(message = value?.failure)

"log" ->
value?.message?.let { message ->
TaskEvent.Log(
level = value.logLevel,
message = message,
)
} ?: run {
Logger.d("Task Event $key missing 'message'")
null
}

"measurement" ->
value?.jsonStr?.let { jsonString ->
TaskEvent.Measurement(
index = value.idx,
json = jsonString,
result =
try {
json.decodeFromString(jsonString)
} catch (e: Exception) {
Logger.d("Could not deserialize $key 'jsonStr'", throwable = e)
null
},
)
} ?: run {
Logger.d("Task Event $key missing 'jsonStr'")
null
}

"status.end" -> TaskEvent.End

"status.geoip_lookup" ->
TaskEvent.GeoIpLookup(
networkName = value?.probeNetworkName,
asn = value?.probeAsn,
ip = value?.probeIp,
countryCode = value?.probeCc,
networkType = networkTypeFinder(),
)

"status.measurement_done" ->
TaskEvent.MeasurementDone(index = value?.idx ?: 0)

"status.measurement_start" ->
value?.input?.ifEmpty { null }?.let { url ->
TaskEvent.MeasurementStart(
index = value.idx,
url = url,
)
} ?: run {
Logger.d("Task Event $key missing 'input'")
null
}

"status.measurement_submission" ->
TaskEvent.MeasurementSubmissionSuccessful(index = value?.idx ?: 0)

"status.progress" ->
value?.percentage?.let { percentageValue ->
TaskEvent.Progress(
percentage = (percentageValue * 100.0).roundToInt(),
message = value.message,
)
} ?: run {
Logger.d("Task Event $key missing 'percentage'")
null
}

"status.report_create" ->
value?.reportId?.let {
TaskEvent.ReportCreate(reportId = it)
} ?: run {
Logger.d("Task Event $key missing 'reportId'")
null
}

"status.started" -> TaskEvent.Started

"task_terminated" -> TaskEvent.TaskTerminated

else -> {
Logger.d("Task Event $key ignored")
null
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.ooni.engine.models

import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.ooni.engine.models.serializers.InstantSerializer

@Serializable
data class MeasurementResult(
@SerialName("probe_asn")
val probeAsn: String? = null,
@SerialName("probe_cc")
val probeCountryCode: String? = null,
@SerialName("test_start_time")
@Serializable(with = InstantSerializer::class)
val testStartTime: Instant? = null,
@SerialName("measurement_start_time")
@Serializable(with = InstantSerializer::class)
val measurementStartTime: Instant? = null,
@SerialName("test_runtime")
val testRuntime: Double? = null,
@SerialName("probe_ip")
val probeIp: String? = null,
@SerialName("report_id")
val reportId: String? = null,
@SerialName("input")
val input: String? = null,
/*
Field `test_keys` is ignored because we're not planning on storing the measurement results
as structured data.
*/
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,41 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

@Serializable(with = NetworkTypeSerializer::class)
enum class NetworkType(val value: String) {
VPN("vpn"),
Wifi("wifi"),
Mobile("mobile"),
NoInternet("no_internet"),
sealed interface NetworkType {
val value: String

data object VPN : NetworkType {
override val value = "vpn"
}

data object Wifi : NetworkType {
override val value = "wifi"
}

data object Mobile : NetworkType {
override val value = "mobile"
}

data object NoInternet : NetworkType {
override val value = "no_internet"
}

data class Unknown(override val value: String) : NetworkType

companion object {
fun fromValue(value: String) =
when (value) {
VPN.value -> VPN
Wifi.value -> Wifi
Mobile.value -> Mobile
NoInternet.value -> NoInternet
else -> Unknown(value)
}
}
}

object NetworkTypeSerializer : KSerializer<NetworkType> {
override val descriptor =
PrimitiveSerialDescriptor("NetworkType", PrimitiveKind.STRING)
override val descriptor = PrimitiveSerialDescriptor("NetworkType", PrimitiveKind.STRING)

override fun serialize(
encoder: Encoder,
Expand All @@ -26,8 +51,5 @@ object NetworkTypeSerializer : KSerializer<NetworkType> {
encoder.encodeString(value.value)
}

override fun deserialize(decoder: Decoder): NetworkType {
val string = decoder.decodeString()
return NetworkType.entries.firstOrNull { it.value == string } ?: NetworkType.NoInternet
}
override fun deserialize(decoder: Decoder): NetworkType = NetworkType.fromValue(decoder.decodeString())
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,67 @@
package org.ooni.engine.models

sealed interface TaskEvent {
data class BugJsonDump(
val value: TaskEventResult.Value,
) : TaskEvent

data object End : TaskEvent

data class GeoIpLookup(
val networkName: String?,
val ip: String?,
val asn: String?,
val countryCode: String?,
val networkType: NetworkType,
) : TaskEvent

data class Log(
val level: String?,
val message: String,
) : TaskEvent

data object Started : TaskEvent
data class Measurement(
val index: Int,
val json: String,
val result: MeasurementResult?,
) : TaskEvent

data class ReportCreate(
val reportId: String,
data class MeasurementDone(
val index: Int,
) : TaskEvent

data class MeasurementStart(
val index: Int,
val url: String,
) : TaskEvent

data class MeasurementSubmissionSuccessful(
val index: Int,
) : TaskEvent

data class MeasurementSubmissionFailure(
val index: Int,
val message: String?,
) : TaskEvent

data class Progress(
val percentage: Int,
val message: String?,
) : TaskEvent

data object StatusEnd : TaskEvent
data class ReportCreate(
val reportId: String,
) : TaskEvent

data object TaskTerminated : TaskEvent
data class ResolverLookupFailure(
val message: String?,
) : TaskEvent

data object Started : TaskEvent

data class FailureStartup(
data class StartupFailure(
val message: String?,
) : TaskEvent

data object TaskTerminated : TaskEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
class EventResult {
class TaskEventResult {
@SerialName("key")
var key: String? = null

Expand Down
Loading

0 comments on commit 9598837

Please sign in to comment.