Make InvocationHandler more robust

This commit is contained in:
Mygod
2021-07-25 00:27:50 -04:00
parent 0c36bcbade
commit 1d6fe3def1
8 changed files with 58 additions and 82 deletions

View File

@@ -71,9 +71,9 @@ enum class TetherType(@DrawableRes val icon: Int) {
} }
@RequiresApi(30) @RequiresApi(30)
override fun onTetherableInterfaceRegexpsChanged(args: Array<out Any?>?) = synchronized(this) { override fun onTetherableInterfaceRegexpsChanged(reg: Any?) = synchronized(this) {
if (requiresUpdate) return@synchronized if (requiresUpdate) return@synchronized
Timber.i("onTetherableInterfaceRegexpsChanged: ${args?.contentDeepToString()}") Timber.i("onTetherableInterfaceRegexpsChanged: $reg")
TetheringManager.unregisterTetheringEventCallback(this) TetheringManager.unregisterTetheringEventCallback(this)
requiresUpdate = true requiresUpdate = true
listener() listener()

View File

@@ -251,13 +251,12 @@ object TetheringManager {
val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).apply { val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).apply {
dexCache(cacheDir) dexCache(cacheDir)
handler { proxy, method, args -> handler { proxy, method, args ->
if (args.isNotEmpty()) Timber.w("Unexpected args for ${method.name}: $args")
@Suppress("NAME_SHADOWING") val callback = reference.get() @Suppress("NAME_SHADOWING") val callback = reference.get()
when (method.name) { if (args.isEmpty()) when (method.name) {
"onTetheringStarted" -> callback?.onTetheringStarted() "onTetheringStarted" -> return@handler callback?.onTetheringStarted()
"onTetheringFailed" -> callback?.onTetheringFailed() "onTetheringFailed" -> return@handler callback?.onTetheringFailed()
else -> ProxyBuilder.callSuper(proxy, method, args)
} }
ProxyBuilder.callSuper(proxy, method, args)
} }
}.build() }.build()
startTetheringLegacy(Services.connectivity, type, showProvisioningUi, proxy, handler) startTetheringLegacy(Services.connectivity, type, showProvisioningUi, proxy, handler)
@@ -279,15 +278,9 @@ object TetheringManager {
arrayOf(interfaceStartTetheringCallback), object : InvocationHandler { arrayOf(interfaceStartTetheringCallback), object : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? { override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
@Suppress("NAME_SHADOWING") val callback = reference.get() @Suppress("NAME_SHADOWING") val callback = reference.get()
return when (val name = method.name) { return when {
"onTetheringStarted" -> { method.matches("onTetheringStarted") -> callback?.onTetheringStarted()
if (!args.isNullOrEmpty()) Timber.w("Unexpected args for $name: $args") method.matches1<Int>("onTetheringFailed") -> callback?.onTetheringFailed(args?.get(0) as Int)
callback?.onTetheringStarted()
}
"onTetheringFailed" -> {
if (args?.size != 1) Timber.w("Unexpected args for $name: $args")
callback?.onTetheringFailed(args?.get(0) as Int)
}
else -> callSuper(interfaceStartTetheringCallback, proxy, method, args) else -> callSuper(interfaceStartTetheringCallback, proxy, method, args)
} }
} }
@@ -449,7 +442,7 @@ object TetheringManager {
* *@param reg The new regular expressions. * *@param reg The new regular expressions.
* @hide * @hide
*/ */
fun onTetherableInterfaceRegexpsChanged(args: Array<out Any?>?) {} fun onTetherableInterfaceRegexpsChanged(reg: Any?) {}
/** /**
* Called when there was a change in the list of tetherable interfaces. Tetherable * Called when there was a change in the list of tetherable interfaces. Tetherable
@@ -545,40 +538,34 @@ object TetheringManager {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? { override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
val callback = reference.get() val callback = reference.get()
val noArgs = args?.size ?: 0 return when {
return when (val name = method.name) { method.matches1<Boolean>("onTetheringSupported") -> {
"onTetheringSupported" -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
callback?.onTetheringSupported(args!![0] as Boolean) callback?.onTetheringSupported(args!![0] as Boolean)
} }
"onUpstreamChanged" -> { method.matches1<Network>("onUpstreamChanged") -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
callback?.onUpstreamChanged(args!![0] as Network?) callback?.onUpstreamChanged(args!![0] as Network?)
} }
"onTetherableInterfaceRegexpsChanged" -> { method.name == "onTetherableInterfaceRegexpsChanged" &&
if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged(args) method.parameters.singleOrNull()?.type?.name ==
"android.net.TetheringManager\$TetheringInterfaceRegexps" -> {
if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged(args!!.single())
regexpsSent = true regexpsSent = true
} }
"onTetherableInterfacesChanged" -> { method.matches1<java.util.List<*>>("onTetherableInterfacesChanged") -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
callback?.onTetherableInterfacesChanged(args!![0] as List<String?>) callback?.onTetherableInterfacesChanged(args!![0] as List<String?>)
} }
"onTetheredInterfacesChanged" -> { method.matches1<java.util.List<*>>("onTetheredInterfacesChanged") -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
callback?.onTetheredInterfacesChanged(args!![0] as List<String?>) callback?.onTetheredInterfacesChanged(args!![0] as List<String?>)
} }
"onError" -> { method.matches2<String, Int>("onError") -> {
if (noArgs != 2) Timber.w("Unexpected args for $name: $args")
callback?.onError(args!![0] as String, args[1] as Int) callback?.onError(args!![0] as String, args[1] as Int)
} }
"onClientsChanged" -> { method.matches1<java.util.Collection<*>>("onClientsChanged") -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
callback?.onClientsChanged(args!![0] as Collection<*>) callback?.onClientsChanged(args!![0] as Collection<*>)
} }
"onOffloadStatusChanged" -> { method.matches1<Int>("onOffloadStatusChanged") -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
callback?.onOffloadStatusChanged(args!![0] as Int) callback?.onOffloadStatusChanged(args!![0] as Int)
} }
else -> callSuper(interfaceTetheringEventCallback, proxy, method, args) else -> callSuper(interfaceTetheringEventCallback, proxy, method, args)

View File

@@ -8,7 +8,7 @@ import be.mygod.vpnhotspot.util.LongConstantLookup
@RequiresApi(30) @RequiresApi(30)
value class SoftApCapability(val inner: Parcelable) { value class SoftApCapability(val inner: Parcelable) {
companion object { companion object {
private val clazz by lazy { Class.forName("android.net.wifi.SoftApCapability") } val clazz by lazy { Class.forName("android.net.wifi.SoftApCapability") }
private val getMaxSupportedClients by lazy { clazz.getDeclaredMethod("getMaxSupportedClients") } private val getMaxSupportedClients by lazy { clazz.getDeclaredMethod("getMaxSupportedClients") }
private val areFeaturesSupported by lazy { clazz.getDeclaredMethod("areFeaturesSupported", Long::class.java) } private val areFeaturesSupported by lazy { clazz.getDeclaredMethod("areFeaturesSupported", Long::class.java) }
@get:RequiresApi(31) @get:RequiresApi(31)

View File

@@ -12,7 +12,7 @@ import timber.log.Timber
@RequiresApi(30) @RequiresApi(30)
value class SoftApInfo(val inner: Parcelable) { value class SoftApInfo(val inner: Parcelable) {
companion object { companion object {
private val clazz by lazy { Class.forName("android.net.wifi.SoftApInfo") } val clazz by lazy { Class.forName("android.net.wifi.SoftApInfo") }
private val getFrequency by lazy { clazz.getDeclaredMethod("getFrequency") } private val getFrequency by lazy { clazz.getDeclaredMethod("getFrequency") }
private val getBandwidth by lazy { clazz.getDeclaredMethod("getBandwidth") } private val getBandwidth by lazy { clazz.getDeclaredMethod("getBandwidth") }
@get:RequiresApi(31) @get:RequiresApi(31)

View File

@@ -10,13 +10,9 @@ import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Parcelable import android.os.Parcelable
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.os.BuildCompat
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat
import be.mygod.vpnhotspot.util.ConstantLookup import be.mygod.vpnhotspot.util.*
import be.mygod.vpnhotspot.util.Services
import be.mygod.vpnhotspot.util.callSuper
import be.mygod.vpnhotspot.util.findIdentifier
import timber.log.Timber import timber.log.Timber
import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method import java.lang.reflect.Method
@@ -260,55 +256,41 @@ object WifiApManager {
} else invokeActual(proxy, method, args) } else invokeActual(proxy, method, args)
private fun invokeActual(proxy: Any, method: Method, args: Array<out Any?>?): Any? { private fun invokeActual(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
val noArgs = args?.size ?: 0 return when {
return when (val name = method.name) { method.matches2<Int, Int>("onStateChanged") -> {
"onStateChanged" -> {
if (noArgs != 2) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
callback.onStateChanged(args!![0] as Int, args[1] as Int) callback.onStateChanged(args!![0] as Int, args[1] as Int)
} }
"onNumClientsChanged" -> @Suppress("DEPRECATION") { method.matches1<Int>("onNumClientsChanged") -> @Suppress("DEPRECATION") {
if (Build.VERSION.SDK_INT >= 30) Timber.w(Exception("Unexpected onNumClientsChanged")) if (Build.VERSION.SDK_INT >= 30) Timber.w(Exception("Unexpected onNumClientsChanged"))
if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
callback.onNumClientsChanged(args!![0] as Int) callback.onNumClientsChanged(args!![0] as Int)
} }
"onConnectedClientsChanged" -> @TargetApi(30) { method.matches1<java.util.List<*>>("onConnectedClientsChanged") -> @TargetApi(30) {
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onConnectedClientsChanged")) if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onConnectedClientsChanged"))
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
when (noArgs) { callback.onConnectedClientsChanged(args!![0] as List<Parcelable>)
1 -> callback.onConnectedClientsChanged(args!![0] as List<Parcelable>)
2 -> null // we use the old method which returns all clients in one call
else -> {
Timber.w("Unexpected args for $name: ${args?.contentToString()}")
null
} }
} method.matches1<java.util.List<*>>("onInfoChanged") -> @TargetApi(31) {
}
"onInfoChanged" -> @TargetApi(30) {
if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
val arg = args!![0]
if (arg is List<*>) {
if (Build.VERSION.SDK_INT < 31) Timber.w(Exception("Unexpected onInfoChanged API 31+")) if (Build.VERSION.SDK_INT < 31) Timber.w(Exception("Unexpected onInfoChanged API 31+"))
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
callback.onInfoChanged(arg as List<Parcelable>) callback.onInfoChanged(args!![0] as List<Parcelable>)
} else { }
method.matches("onInfoChanged", SoftApInfo.clazz) -> @TargetApi(30) {
when (Build.VERSION.SDK_INT) { when (Build.VERSION.SDK_INT) {
30 -> { } 30 -> { }
in 31..Int.MAX_VALUE -> return null // ignore old version calls in 31..Int.MAX_VALUE -> return null // ignore old version calls
else -> Timber.w(Exception("Unexpected onInfoChanged API 30")) else -> Timber.w(Exception("Unexpected onInfoChanged API 30"))
} }
val arg = args!![0]
val info = SoftApInfo(arg as Parcelable) val info = SoftApInfo(arg as Parcelable)
callback.onInfoChanged( // check for legacy empty info with CHANNEL_WIDTH_INVALID callback.onInfoChanged( // check for legacy empty info with CHANNEL_WIDTH_INVALID
if (info.frequency == 0 && info.bandwidth == 0) emptyList() else listOf(arg)) if (info.frequency == 0 && info.bandwidth == 0) emptyList() else listOf(arg))
} }
} method.matches("onCapabilityChanged", SoftApCapability.clazz) -> @TargetApi(30) {
"onCapabilityChanged" -> @TargetApi(30) {
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onCapabilityChanged")) if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onCapabilityChanged"))
if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
callback.onCapabilityChanged(args!![0] as Parcelable) callback.onCapabilityChanged(args!![0] as Parcelable)
} }
"onBlockedClientConnecting" -> @TargetApi(30) { method.matches("onBlockedClientConnecting", WifiClient.clazz, Int::class.java) -> @TargetApi(30) {
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onBlockedClientConnecting")) if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onBlockedClientConnecting"))
if (noArgs != 2) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
callback.onBlockedClientConnecting(args!![0] as Parcelable, args[1] as Int) callback.onBlockedClientConnecting(args!![0] as Parcelable, args[1] as Int)
} }
else -> callSuper(interfaceSoftApCallback, proxy, method, args) else -> callSuper(interfaceSoftApCallback, proxy, method, args)

View File

@@ -11,7 +11,7 @@ import timber.log.Timber
@RequiresApi(30) @RequiresApi(30)
value class WifiClient(val inner: Parcelable) { value class WifiClient(val inner: Parcelable) {
companion object { companion object {
private val clazz by lazy { Class.forName("android.net.wifi.WifiClient") } val clazz by lazy { Class.forName("android.net.wifi.WifiClient") }
private val getMacAddress by lazy { clazz.getDeclaredMethod("getMacAddress") } private val getMacAddress by lazy { clazz.getDeclaredMethod("getMacAddress") }
@get:RequiresApi(31) @get:RequiresApi(31)
private val getApInstanceIdentifier by lazy @TargetApi(31) { UnblockCentral.getApInstanceIdentifier(clazz) } private val getApInstanceIdentifier by lazy @TargetApi(31) { UnblockCentral.getApInstanceIdentifier(clazz) }

View File

@@ -9,8 +9,8 @@ import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.MacAddressCompat import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.util.callSuper import be.mygod.vpnhotspot.util.callSuper
import be.mygod.vpnhotspot.util.matches1
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import timber.log.Timber
import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method import java.lang.reflect.Method
import java.lang.reflect.Proxy import java.lang.reflect.Proxy
@@ -117,9 +117,8 @@ object WifiP2pManagerHelper {
val result = CompletableDeferred<Collection<WifiP2pGroup>>() val result = CompletableDeferred<Collection<WifiP2pGroup>>()
requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader, requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader,
arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler { arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when (method.name) { override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when {
"onPersistentGroupInfoAvailable" -> { method.matches1<java.util.Collection<*>>("onPersistentGroupInfoAvailable") -> {
if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args"))
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
result.complete(getGroupList(args!![0]) as Collection<WifiP2pGroup>) result.complete(getGroupList(args!![0]) as Collection<WifiP2pGroup>)
} }

View File

@@ -53,6 +53,14 @@ fun Long.toPluralInt(): Int {
return (this % 1000000000).toInt() + 1000000000 return (this % 1000000000).toInt() + 1000000000
} }
@RequiresApi(26)
fun Method.matches(name: String, vararg classes: Class<*>) = this.name == name && parameterCount == classes.size &&
(0 until parameterCount).all { i -> parameters[i].type == classes[i] }
@RequiresApi(26)
inline fun <reified T> Method.matches1(name: String) = matches(name, T::class.java)
@RequiresApi(26)
inline fun <reified T0, reified T1> Method.matches2(name: String) = matches(name, T0::class.java, T1::class.java)
fun Context.ensureReceiverUnregistered(receiver: BroadcastReceiver) { fun Context.ensureReceiverUnregistered(receiver: BroadcastReceiver) {
try { try {
unregisterReceiver(receiver) unregisterReceiver(receiver)