From 1d6fe3def10cff25b5a63ca58864309f9805c1fa Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 25 Jul 2021 00:27:50 -0400 Subject: [PATCH] Make InvocationHandler more robust --- .../be/mygod/vpnhotspot/net/TetherType.kt | 4 +- .../mygod/vpnhotspot/net/TetheringManager.kt | 53 ++++++---------- .../vpnhotspot/net/wifi/SoftApCapability.kt | 2 +- .../mygod/vpnhotspot/net/wifi/SoftApInfo.kt | 2 +- .../vpnhotspot/net/wifi/WifiApManager.kt | 62 +++++++------------ .../mygod/vpnhotspot/net/wifi/WifiClient.kt | 2 +- .../net/wifi/WifiP2pManagerHelper.kt | 7 +-- .../java/be/mygod/vpnhotspot/util/Utils.kt | 8 +++ 8 files changed, 58 insertions(+), 82 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt index 18b6ef72..aad28004 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt @@ -71,9 +71,9 @@ enum class TetherType(@DrawableRes val icon: Int) { } @RequiresApi(30) - override fun onTetherableInterfaceRegexpsChanged(args: Array?) = synchronized(this) { + override fun onTetherableInterfaceRegexpsChanged(reg: Any?) = synchronized(this) { if (requiresUpdate) return@synchronized - Timber.i("onTetherableInterfaceRegexpsChanged: ${args?.contentDeepToString()}") + Timber.i("onTetherableInterfaceRegexpsChanged: $reg") TetheringManager.unregisterTetheringEventCallback(this) requiresUpdate = true listener() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt index 932eecbd..dd9da97c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt @@ -251,13 +251,12 @@ object TetheringManager { val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).apply { dexCache(cacheDir) handler { proxy, method, args -> - if (args.isNotEmpty()) Timber.w("Unexpected args for ${method.name}: $args") @Suppress("NAME_SHADOWING") val callback = reference.get() - when (method.name) { - "onTetheringStarted" -> callback?.onTetheringStarted() - "onTetheringFailed" -> callback?.onTetheringFailed() - else -> ProxyBuilder.callSuper(proxy, method, args) + if (args.isEmpty()) when (method.name) { + "onTetheringStarted" -> return@handler callback?.onTetheringStarted() + "onTetheringFailed" -> return@handler callback?.onTetheringFailed() } + ProxyBuilder.callSuper(proxy, method, args) } }.build() startTetheringLegacy(Services.connectivity, type, showProvisioningUi, proxy, handler) @@ -279,15 +278,9 @@ object TetheringManager { arrayOf(interfaceStartTetheringCallback), object : InvocationHandler { override fun invoke(proxy: Any, method: Method, args: Array?): Any? { @Suppress("NAME_SHADOWING") val callback = reference.get() - return when (val name = method.name) { - "onTetheringStarted" -> { - if (!args.isNullOrEmpty()) Timber.w("Unexpected args for $name: $args") - callback?.onTetheringStarted() - } - "onTetheringFailed" -> { - if (args?.size != 1) Timber.w("Unexpected args for $name: $args") - callback?.onTetheringFailed(args?.get(0) as Int) - } + return when { + method.matches("onTetheringStarted") -> callback?.onTetheringStarted() + method.matches1("onTetheringFailed") -> callback?.onTetheringFailed(args?.get(0) as Int) else -> callSuper(interfaceStartTetheringCallback, proxy, method, args) } } @@ -449,7 +442,7 @@ object TetheringManager { * *@param reg The new regular expressions. * @hide */ - fun onTetherableInterfaceRegexpsChanged(args: Array?) {} + fun onTetherableInterfaceRegexpsChanged(reg: Any?) {} /** * 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?): Any? { @Suppress("NAME_SHADOWING") val callback = reference.get() - val noArgs = args?.size ?: 0 - return when (val name = method.name) { - "onTetheringSupported" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + return when { + method.matches1("onTetheringSupported") -> { callback?.onTetheringSupported(args!![0] as Boolean) } - "onUpstreamChanged" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + method.matches1("onUpstreamChanged") -> { callback?.onUpstreamChanged(args!![0] as Network?) } - "onTetherableInterfaceRegexpsChanged" -> { - if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged(args) + method.name == "onTetherableInterfaceRegexpsChanged" && + method.parameters.singleOrNull()?.type?.name == + "android.net.TetheringManager\$TetheringInterfaceRegexps" -> { + if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged(args!!.single()) regexpsSent = true } - "onTetherableInterfacesChanged" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + method.matches1>("onTetherableInterfacesChanged") -> { @Suppress("UNCHECKED_CAST") callback?.onTetherableInterfacesChanged(args!![0] as List) } - "onTetheredInterfacesChanged" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + method.matches1>("onTetheredInterfacesChanged") -> { @Suppress("UNCHECKED_CAST") callback?.onTetheredInterfacesChanged(args!![0] as List) } - "onError" -> { - if (noArgs != 2) Timber.w("Unexpected args for $name: $args") + method.matches2("onError") -> { callback?.onError(args!![0] as String, args[1] as Int) } - "onClientsChanged" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + method.matches1>("onClientsChanged") -> { callback?.onClientsChanged(args!![0] as Collection<*>) } - "onOffloadStatusChanged" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + method.matches1("onOffloadStatusChanged") -> { callback?.onOffloadStatusChanged(args!![0] as Int) } else -> callSuper(interfaceTetheringEventCallback, proxy, method, args) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt index 24432405..dbed4cc0 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt @@ -8,7 +8,7 @@ import be.mygod.vpnhotspot.util.LongConstantLookup @RequiresApi(30) value class SoftApCapability(val inner: Parcelable) { 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 areFeaturesSupported by lazy { clazz.getDeclaredMethod("areFeaturesSupported", Long::class.java) } @get:RequiresApi(31) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt index c218f0bf..167534d5 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt @@ -12,7 +12,7 @@ import timber.log.Timber @RequiresApi(30) value class SoftApInfo(val inner: Parcelable) { 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 getBandwidth by lazy { clazz.getDeclaredMethod("getBandwidth") } @get:RequiresApi(31) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 0a83397f..46438958 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -10,13 +10,9 @@ import android.os.Build import android.os.Handler import android.os.Parcelable import androidx.annotation.RequiresApi -import androidx.core.os.BuildCompat import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat -import be.mygod.vpnhotspot.util.ConstantLookup -import be.mygod.vpnhotspot.util.Services -import be.mygod.vpnhotspot.util.callSuper -import be.mygod.vpnhotspot.util.findIdentifier +import be.mygod.vpnhotspot.util.* import timber.log.Timber import java.lang.reflect.InvocationHandler import java.lang.reflect.Method @@ -260,55 +256,41 @@ object WifiApManager { } else invokeActual(proxy, method, args) private fun invokeActual(proxy: Any, method: Method, args: Array?): Any? { - val noArgs = args?.size ?: 0 - return when (val name = method.name) { - "onStateChanged" -> { - if (noArgs != 2) Timber.w("Unexpected args for $name: ${args?.contentToString()}") + return when { + method.matches2("onStateChanged") -> { callback.onStateChanged(args!![0] as Int, args[1] as Int) } - "onNumClientsChanged" -> @Suppress("DEPRECATION") { + method.matches1("onNumClientsChanged") -> @Suppress("DEPRECATION") { 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) } - "onConnectedClientsChanged" -> @TargetApi(30) { + method.matches1>("onConnectedClientsChanged") -> @TargetApi(30) { if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onConnectedClientsChanged")) @Suppress("UNCHECKED_CAST") - when (noArgs) { - 1 -> callback.onConnectedClientsChanged(args!![0] as List) - 2 -> null // we use the old method which returns all clients in one call - else -> { - Timber.w("Unexpected args for $name: ${args?.contentToString()}") - null - } - } + callback.onConnectedClientsChanged(args!![0] as List) } - "onInfoChanged" -> @TargetApi(30) { - if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}") + method.matches1>("onInfoChanged") -> @TargetApi(31) { + if (Build.VERSION.SDK_INT < 31) Timber.w(Exception("Unexpected onInfoChanged API 31+")) + @Suppress("UNCHECKED_CAST") + callback.onInfoChanged(args!![0] as List) + } + method.matches("onInfoChanged", SoftApInfo.clazz) -> @TargetApi(30) { + when (Build.VERSION.SDK_INT) { + 30 -> { } + in 31..Int.MAX_VALUE -> return null // ignore old version calls + else -> Timber.w(Exception("Unexpected onInfoChanged API 30")) + } val arg = args!![0] - if (arg is List<*>) { - if (Build.VERSION.SDK_INT < 31) Timber.w(Exception("Unexpected onInfoChanged API 31+")) - @Suppress("UNCHECKED_CAST") - callback.onInfoChanged(arg as List) - } else { - when (Build.VERSION.SDK_INT) { - 30 -> { } - in 31..Int.MAX_VALUE -> return null // ignore old version calls - else -> Timber.w(Exception("Unexpected onInfoChanged API 30")) - } - val info = SoftApInfo(arg as Parcelable) - callback.onInfoChanged( // check for legacy empty info with CHANNEL_WIDTH_INVALID - if (info.frequency == 0 && info.bandwidth == 0) emptyList() else listOf(arg)) - } + val info = SoftApInfo(arg as Parcelable) + callback.onInfoChanged( // check for legacy empty info with CHANNEL_WIDTH_INVALID + if (info.frequency == 0 && info.bandwidth == 0) emptyList() else listOf(arg)) } - "onCapabilityChanged" -> @TargetApi(30) { + method.matches("onCapabilityChanged", SoftApCapability.clazz) -> @TargetApi(30) { 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) } - "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 (noArgs != 2) Timber.w("Unexpected args for $name: ${args?.contentToString()}") callback.onBlockedClientConnecting(args!![0] as Parcelable, args[1] as Int) } else -> callSuper(interfaceSoftApCallback, proxy, method, args) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiClient.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiClient.kt index 57edc1a3..b106db9d 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiClient.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiClient.kt @@ -11,7 +11,7 @@ import timber.log.Timber @RequiresApi(30) value class WifiClient(val inner: Parcelable) { 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") } @get:RequiresApi(31) private val getApInstanceIdentifier by lazy @TargetApi(31) { UnblockCentral.getApInstanceIdentifier(clazz) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt index 2f26d7a3..f95c87d9 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt @@ -9,8 +9,8 @@ import androidx.annotation.RequiresApi import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.MacAddressCompat import be.mygod.vpnhotspot.util.callSuper +import be.mygod.vpnhotspot.util.matches1 import kotlinx.coroutines.CompletableDeferred -import timber.log.Timber import java.lang.reflect.InvocationHandler import java.lang.reflect.Method import java.lang.reflect.Proxy @@ -117,9 +117,8 @@ object WifiP2pManagerHelper { val result = CompletableDeferred>() requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader, arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler { - override fun invoke(proxy: Any, method: Method, args: Array?): Any? = when (method.name) { - "onPersistentGroupInfoAvailable" -> { - if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args")) + override fun invoke(proxy: Any, method: Method, args: Array?): Any? = when { + method.matches1>("onPersistentGroupInfoAvailable") -> { @Suppress("UNCHECKED_CAST") result.complete(getGroupList(args!![0]) as Collection) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index 6d24d6b2..49cb547f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -53,6 +53,14 @@ fun Long.toPluralInt(): Int { 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 Method.matches1(name: String) = matches(name, T::class.java) +@RequiresApi(26) +inline fun Method.matches2(name: String) = matches(name, T0::class.java, T1::class.java) + fun Context.ensureReceiverUnregistered(receiver: BroadcastReceiver) { try { unregisterReceiver(receiver)