-
Couldn't load subscription status.
- Fork 6
chore: draft for using geoipdb from path
#940
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d49760f
9c30e78
188221a
eef3dbc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package org.ooni.probe.net | ||
|
|
||
| import kotlinx.coroutines.Dispatchers | ||
| import kotlinx.coroutines.withContext | ||
| import org.ooni.engine.models.Failure | ||
| import org.ooni.engine.models.Result | ||
| import org.ooni.engine.models.Success | ||
| import org.ooni.probe.data.models.GetBytesException | ||
| import java.net.HttpURLConnection | ||
| import java.net.URL | ||
|
|
||
| actual suspend fun httpGetBytes(url: String): Result<ByteArray, GetBytesException> = | ||
| withContext(Dispatchers.IO) { | ||
| val connection: HttpURLConnection | ||
| try { | ||
| connection = URL(url).openConnection() as HttpURLConnection | ||
| } catch (e: Throwable) { | ||
| return@withContext Failure(GetBytesException(e)) | ||
| } | ||
|
|
||
| connection.requestMethod = "GET" | ||
| connection.instanceFollowRedirects = true | ||
| connection.connectTimeout = 15000 | ||
| connection.readTimeout = 30000 | ||
| try { | ||
| val code = connection.responseCode | ||
| val stream = if (code in 200..299) connection.inputStream else connection.errorStream | ||
| val bytes = stream?.use { it.readBytes() } ?: ByteArray(0) | ||
| if (code !in 200..299) { | ||
| Failure(GetBytesException(RuntimeException("HTTP $code while GET $url: ${String(bytes)}"))) | ||
| } else { | ||
| Success(bytes) | ||
| } | ||
| } catch (e: Throwable) { | ||
| Failure(GetBytesException(e)) | ||
| } finally { | ||
| connection.disconnect() | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -148,7 +148,7 @@ class Engine( | |
|
|
||
| private fun session(sessionConfig: OonimkallBridge.SessionConfig): OonimkallBridge.Session = bridge.newSession(sessionConfig) | ||
|
|
||
| private fun buildTaskSettings( | ||
| private suspend fun buildTaskSettings( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| netTest: NetTest, | ||
| taskOrigin: TaskOrigin, | ||
| preferences: EnginePreferences, | ||
|
|
@@ -166,6 +166,7 @@ class Engine( | |
| tunnelDir = "$baseFilePath/tunnel", | ||
| tempDir = cacheDir, | ||
| assetsDir = "$baseFilePath/assets", | ||
| geoIpDB = preferences.geoipDbVersion?.let { "$cacheDir/$it.mmdb" }, | ||
| options = TaskSettings.Options( | ||
| noCollector = !preferences.uploadResults, | ||
| softwareName = buildSoftwareName(taskOrigin), | ||
|
|
@@ -208,6 +209,7 @@ class Engine( | |
| tunnelDir = "$baseFilePath/tunnel", | ||
| tempDir = cacheDir, | ||
| assetsDir = "$baseFilePath/assets", | ||
| geoIpDB = preferences.geoipDbVersion?.let { "$cacheDir/$it.mmdb" }, | ||
| logger = oonimkallLogger, | ||
| verbose = false, | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,5 +7,6 @@ data class EnginePreferences( | |
| val taskLogLevel: TaskLogLevel, | ||
| val uploadResults: Boolean, | ||
| val proxy: String?, | ||
| val geoipDbVersion: String?, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we also need to tell the engine what version of the Db? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is more to determine the path to the |
||
| val maxRuntime: Duration?, | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ sealed interface TaskEvent { | |
| val ip: String?, | ||
| val asn: String?, | ||
| val countryCode: String?, | ||
| val geoIpdb: String?, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to get the geoIpDb from the engine? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To determine which version of |
||
| val networkType: NetworkType, | ||
| ) : TaskEvent | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package org.ooni.probe.data.models | ||
|
|
||
| class GetBytesException( | ||
| t: Throwable, | ||
| ) : Exception(t) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package org.ooni.probe.domain | ||
|
|
||
| import okio.FileSystem | ||
| import okio.Path | ||
| import okio.Path.Companion.toPath | ||
| import okio.buffer | ||
| import okio.use | ||
| import org.ooni.engine.models.Result | ||
| import org.ooni.probe.data.models.GetBytesException | ||
|
|
||
| /** | ||
| * Downloads binary content to a target absolute path using the provided fetcher. | ||
| * - Creates parent directories if needed | ||
| * - Skips writing if the target already exists with the same size | ||
| */ | ||
| class DownloadFile( | ||
| private val fileSystem: FileSystem, | ||
| private val fetchBytes: suspend (url: String) -> Result<ByteArray, GetBytesException>, | ||
| ) { | ||
| suspend operator fun invoke( | ||
| url: String, | ||
| absoluteTargetPath: String, | ||
| ): Result<Path, GetBytesException> { | ||
| val target = absoluteTargetPath.toPath() | ||
| target.parent?.let { parent -> | ||
| if (fileSystem.metadataOrNull(parent) == null) fileSystem.createDirectories(parent) | ||
| } | ||
| return fetchBytes(url).map { bytes -> | ||
| fileSystem.sink(target).buffer().use { sink -> sink.write(bytes) } | ||
| target | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| package org.ooni.probe.domain | ||
|
|
||
| import co.touchlab.kermit.Logger | ||
| import kotlinx.coroutines.flow.first | ||
| import kotlinx.serialization.SerialName | ||
| import kotlinx.serialization.Serializable | ||
| import kotlinx.serialization.SerializationException | ||
| import kotlinx.serialization.json.Json | ||
| import okio.Path | ||
| import ooniprobe.composeapp.generated.resources.Res | ||
| import ooniprobe.composeapp.generated.resources.engine_mmdb_version | ||
| import org.jetbrains.compose.resources.getString | ||
| import org.ooni.engine.Engine | ||
| import org.ooni.engine.Engine.MkException | ||
| import org.ooni.engine.models.Failure | ||
| import org.ooni.engine.models.Result | ||
| import org.ooni.engine.models.Success | ||
| import org.ooni.engine.models.TaskOrigin | ||
| import org.ooni.probe.data.models.GetBytesException | ||
| import org.ooni.probe.data.models.SettingsKey | ||
| import org.ooni.probe.data.repositories.PreferenceRepository | ||
| import kotlin.time.Clock | ||
|
|
||
| class FetchGeoIpDbUpdates( | ||
| private val downloadFile: suspend (url: String, absoluteTargetPath: String) -> Result<Path, GetBytesException>, | ||
| private val cacheDir: String, | ||
| private val engineHttpDo: suspend (method: String, url: String, taskOrigin: TaskOrigin) -> Result<String?, Engine.MkException>, | ||
| private val preferencesRepository: PreferenceRepository, | ||
| private val json: Json, | ||
| ) { | ||
| suspend operator fun invoke(): Result<Path?, Engine.MkException> = | ||
| getLatestEngineVersion() | ||
| .onSuccess { version -> | ||
| val (isLatest, _, latestVersion) = isGeoIpDbLatest(version) | ||
| if (isLatest) { | ||
| return Success(null) | ||
| } else { | ||
| val versionName = latestVersion | ||
| val url = buildGeoIpDbUrl(versionName) | ||
| val target = "$cacheDir/$versionName.mmdb" | ||
|
|
||
| downloadFile(url, target) | ||
| .onSuccess { downloadedPath -> | ||
| preferencesRepository.setValueByKey( | ||
| SettingsKey.MMDB_VERSION, | ||
| versionName, | ||
| ) | ||
| preferencesRepository.setValueByKey( | ||
| SettingsKey.MMDB_LAST_CHECK, | ||
| Clock.System.now().toEpochMilliseconds(), | ||
| ) | ||
| return Success(downloadedPath) | ||
| }.onFailure { downloadError -> | ||
| return Failure(Engine.MkException(downloadError)) | ||
| } | ||
| } | ||
| }.onFailure { versionError -> | ||
| return Failure(versionError) | ||
| }.let { Failure(Engine.MkException(Throwable("Unexpected state"))) } | ||
|
|
||
| /** | ||
| * Compare latest and current version integers and return pair of latest state and actual version number | ||
| * @return Triple<Boolean, String, String> where the first element is true if the DB is the latest, | ||
| * the second is the current version and the third is the latest version. | ||
| */ | ||
| private suspend fun isGeoIpDbLatest(latestVersion: String): Triple<Boolean, String, String> { | ||
| val currentGeoIpDbVersion: String = | ||
| ( | ||
| preferencesRepository.getValueByKey(SettingsKey.MMDB_VERSION).first() | ||
| ?: getString(Res.string.engine_mmdb_version) | ||
| ) as String | ||
|
|
||
| return Triple( | ||
| normalize(currentGeoIpDbVersion) >= normalize(latestVersion), | ||
| currentGeoIpDbVersion, | ||
| latestVersion, | ||
| ) | ||
| } | ||
|
|
||
| private suspend fun getLatestEngineVersion(): Result<String, MkException> { | ||
| val url = "https://api.github.com/repos/aanorbel/oomplt-mmdb/releases/latest" | ||
|
|
||
| return engineHttpDo("GET", url, TaskOrigin.OoniRun).map { payload -> | ||
| payload?.let { | ||
| try { | ||
| json.decodeFromString(GhRelease.serializer(), payload).tag | ||
| } catch (e: SerializationException) { | ||
| Logger.e(e) { "Failed to decode release info" } | ||
| null | ||
| } catch (e: IllegalArgumentException) { | ||
| Logger.e(e) { "Failed to decode release info" } | ||
| null | ||
| } | ||
| } ?: throw MkException(Throwable("Failed to fetch latest version")) | ||
| } | ||
| } | ||
|
|
||
| private fun buildGeoIpDbUrl(version: String): String = | ||
| "https://github.com/aanorbel/oomplt-mmdb/releases/download/$version/$version-ip2country_as.mmdb" | ||
|
|
||
| private fun normalize(tag: String): Int = tag.removePrefix("v").trim().toInt() | ||
|
|
||
| @Serializable | ||
| data class GhRelease( | ||
| @SerialName("tag_name") val tag: String, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's easier if this is a regular constant, instead of a string resource? We're not taking advantage of the resource system for anything here.