Implement IP2Country

This commit is contained in:
nielsandriesse 2020-06-02 15:00:37 +10:00
parent 0a9f1f0e23
commit 51f0374109
2 changed files with 118 additions and 0 deletions

View File

@ -8,6 +8,7 @@ buildscript {
ext.rss_parser_version = "2.0.4" ext.rss_parser_version = "2.0.4"
ext.google_services_version = "4.3.3" ext.google_services_version = "4.3.3"
ext.firebase_messaging_version = "18.0.0" ext.firebase_messaging_version = "18.0.0"
ext.opencsv_version = "4.6"
repositories { repositories {
mavenLocal() mavenLocal()
@ -188,6 +189,7 @@ dependencies {
implementation "com.jakewharton.rxbinding3:rxbinding:3.1.0" implementation "com.jakewharton.rxbinding3:rxbinding:3.1.0"
implementation "com.github.tbruyelle:rxpermissions:0.10.2" implementation "com.github.tbruyelle:rxpermissions:0.10.2"
implementation "com.github.ybq:Android-SpinKit:1.4.0" implementation "com.github.ybq:Android-SpinKit:1.4.0"
implementation "com.opencsv:opencsv:$opencsv_version"
} }
def canonicalVersionCode = 52 def canonicalVersionCode = 52

View File

@ -0,0 +1,116 @@
package org.thoughtcrime.securesms.loki.utilities
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.support.v4.content.LocalBroadcastManager
import android.util.Log
import com.opencsv.CSVReader
import org.whispersystems.signalservice.loki.api.onionrequests.OnionRequestAPI
import java.io.File
import java.io.FileOutputStream
import java.io.FileReader
class IP2Country private constructor(private val context: Context) {
private val pathsBuiltEventReceiver: BroadcastReceiver
private val countryNamesCache = mutableMapOf<String, String>()
private val ipv4Table by lazy {
loadFile("geolite2_country_blocks_ipv4.csv")
}
private val countryNamesTable by lazy {
loadFile("geolite2_country_locations_english.csv")
}
// region Initialization
companion object {
public lateinit var shared: IP2Country
public fun configureIfNeeded(context: Context) {
if (::shared.isInitialized) { return; }
shared = IP2Country(context)
}
}
init {
preloadCountriesIfNeeded()
pathsBuiltEventReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
preloadCountriesIfNeeded()
}
}
LocalBroadcastManager.getInstance(context).registerReceiver(pathsBuiltEventReceiver, IntentFilter("pathsBuilt"))
}
// TODO: Deinit?
// endregion
// region Implementation
private fun loadFile(fileName: String): File {
val directory = File(context.applicationInfo.dataDir)
val file = File(directory, fileName)
if (directory.list().contains(fileName)) { return file }
val inputStream = context.assets.open("csv/$fileName")
val outputStream = FileOutputStream(file)
val buffer = ByteArray(1024)
while (true) {
val count = inputStream.read(buffer)
if (count < 0) { break }
outputStream.write(buffer, 0, count)
}
inputStream.close()
outputStream.close()
return file
}
private fun getCountry(ip: String): String {
var truncatedIP = ip
fun getCountryInternal(): String {
val country = countryNamesCache[ip]
if (country != null) { return country }
val ipv4TableReader = CSVReader(FileReader(ipv4Table.absoluteFile))
val countryNamesTableReader = CSVReader(FileReader(ipv4Table.absoluteFile))
var ipv4TableLine = ipv4TableReader.readNext()
while (ipv4TableLine != null) {
if (!ipv4TableLine[0].startsWith(truncatedIP)) {
ipv4TableLine = ipv4TableReader.readNext()
continue
}
val countryID = ipv4TableLine[1]
var countryNamesTableLine = countryNamesTableReader.readNext()
while (countryNamesTableLine != null) {
if (countryNamesTableLine[0] != countryID) {
countryNamesTableLine = countryNamesTableReader.readNext()
continue
}
@Suppress("NAME_SHADOWING") val country = countryNamesTableLine[5]
countryNamesCache[ip] = country
return country
}
}
if (truncatedIP.contains(".") && !truncatedIP.endsWith(".")) { // The fuzziest we want to go is xxx.x
truncatedIP.dropLast(1)
if (truncatedIP.endsWith(".")) { truncatedIP.dropLast(1) }
return getCountryInternal()
} else {
return "Unknown Country"
}
}
return getCountryInternal()
}
private fun preloadCountriesIfNeeded() {
Thread {
val path = OnionRequestAPI.paths.firstOrNull() ?: return@Thread
path.forEach { snode ->
getCountry(snode.ip) // Preload if needed
}
Log.d("Loki", "Finished preloading onion request path countries.")
}.start()
}
// endregion
}