Files
vpnhotspotmod/mobile/src/main/java/be/mygod/vpnhotspot/client/MacLookup.kt
2023-02-08 21:37:16 -05:00

82 lines
3.3 KiB
Kotlin

package be.mygod.vpnhotspot.client
import android.content.Context
import android.net.MacAddress
import androidx.annotation.MainThread
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber
import java.net.HttpURLConnection
import java.net.URL
/**
* This class generates a default nickname for new clients.
*/
object MacLookup {
class UnexpectedError(val mac: MacAddress, val error: String) : JSONException("") {
private fun formatMessage(context: Context) =
context.getString(R.string.clients_mac_lookup_unexpected_error,
mac.toByteArray().joinToString("") { "%02x".format(it) }.substring(0, 9), error)
override val message get() = formatMessage(app.english)
override fun getLocalizedMessage() = formatMessage(app)
}
private val macLookupBusy = mutableMapOf<MacAddress, Pair<HttpURLConnection, Job>>()
// http://en.wikipedia.org/wiki/ISO_3166-1
private val countryCodeRegex = "(?:^|[^A-Z])([A-Z]{2})[\\s\\d]*$".toRegex()
@MainThread
fun abort(mac: MacAddress) = macLookupBusy.remove(mac)?.let { (conn, job) ->
job.cancel()
conn.disconnect()
}
@MainThread
fun perform(mac: MacAddress, explicit: Boolean = false) {
abort(mac)
val conn = URL("https://macvendors.co/api/$mac").openConnection() as HttpURLConnection
macLookupBusy[mac] = conn to GlobalScope.launch(Dispatchers.IO) {
try {
val response = conn.inputStream.bufferedReader().readText()
val obj = JSONObject(response).getJSONObject("result")
obj.opt("error")?.also { throw UnexpectedError(mac, it.toString()) }
val company = obj.getString("company")
val match = extractCountry(mac, response, obj)
val result = if (match != null) {
String(match.groupValues[1].flatMap { listOf('\uD83C', it + 0xDDA5) }.toCharArray()) + ' ' + company
} else company
AppDatabase.instance.clientRecordDao.upsert(mac) {
nickname = result
macLookupPending = false
}
} catch (e: JSONException) {
if ((e as? UnexpectedError)?.error == "no result") {
// no vendor found, we should not retry in the future
AppDatabase.instance.clientRecordDao.upsert(mac) { macLookupPending = false }
} else Timber.w(e)
if (explicit) SmartSnackbar.make(e).show()
} catch (e: Throwable) {
Timber.d(e)
if (explicit) SmartSnackbar.make(e).show()
}
}
}
private fun extractCountry(mac: MacAddress, response: String, obj: JSONObject): MatchResult? {
countryCodeRegex.matchEntire(obj.optString("country"))?.also { return it }
val address = obj.optString("address")
if (address.isBlank()) return null
countryCodeRegex.find(address)?.also { return it }
Timber.w(UnexpectedError(mac, response))
return null
}
}