Implement MAC lookup (#68)

* Implement MAC lookup

* Refine error processing

* Use long to store MAC consistently

* Link back to macvendors.co

* Undo some havoc

* Do not show mac spans for TV

* Show MAC and IP in a consistent order

* Add IP spans by ipinfo.io

* Add SpanFormatter

* Fix IPv6 ipinfo.io link

* Refine SpanFormatter

* Fix pressing the link
This commit is contained in:
Mygod
2019-01-26 21:20:40 +08:00
committed by GitHub
parent 94114f7a4b
commit d4208affbb
38 changed files with 562 additions and 112 deletions

View File

@@ -0,0 +1,62 @@
package be.mygod.vpnhotspot.client
import androidx.annotation.MainThread
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.room.macToString
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.io.IOException
import java.net.HttpURLConnection
import java.net.URL
/**
* This class generates a default nickname for new clients.
*/
object MacLookup {
class UnexpectedError(mac: Long, val error: String) :
JSONException("Server returned error for ${mac.macToString()}: $error")
private val macLookupBusy = mutableMapOf<Long, Pair<HttpURLConnection, Job>>()
private val countryCodeRegex = "[A-Z]{2}".toRegex()
@MainThread
fun abort(mac: Long) = macLookupBusy.remove(mac)?.let { (conn, job) ->
job.cancel()
conn.disconnect()
}
@MainThread
fun perform(mac: Long) {
abort(mac)
val conn = URL("https://macvendors.co/api/" + mac.macToString()).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.optString("error", null)?.also { throw UnexpectedError(mac, it) }
val company = obj.getString("company")
val country = obj.optString("country")
if (countryCodeRegex.matchEntire(country) == null) Timber.w(UnexpectedError(mac, response))
val result = if (country != null) {
String(country.flatMap { listOf('\uD83C', it + 0xDDA5) }.toCharArray()) + ' ' + company
} else company
AppDatabase.instance.clientRecordDao.upsert(mac) {
nickname = result
macLookupPending = false
}
} catch (e: IOException) {
Timber.d(e)
} 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)
}
}
}
}