Skip to content

Commit

Permalink
android: improved error handling for missing multicast permission
Browse files Browse the repository at this point in the history
- startDiscovery() and stopDiscovery() will now throw proper NsdError if multicast locking is not available
- extended NsdError for security issues
- moved the permission to the main manifest in the example application
- now converting PlatformError back to NsdError on platform side
  • Loading branch information
sebastianhaberey committed Apr 26, 2022
1 parent d907161 commit 65d9e7e
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 16 deletions.
1 change: 0 additions & 1 deletion nsd/example/android/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
</manifest>
1 change: 1 addition & 0 deletions nsd/example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.haberey.flutter.example">

<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<application
android:icon="@mipmap/ic_launcher"
android:label="example">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package com.haberey.flutter.nsd_android

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.net.wifi.WifiManager
import android.os.Handler
import android.os.Looper
import androidx.annotation.NonNull
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.lang.Error
import java.util.concurrent.Semaphore
import kotlin.collections.HashMap
import kotlin.concurrent.thread
Expand All @@ -23,23 +26,26 @@ class NsdAndroidPlugin : FlutterPlugin, MethodCallHandler {

private lateinit var nsdManager: NsdManager
private lateinit var wifiManager: WifiManager
private lateinit var multicastLock: WifiManager.MulticastLock
private lateinit var methodChannel: MethodChannel

private var multicastLock: WifiManager.MulticastLock? = null

private val discoveryListeners = HashMap<String, NsdManager.DiscoveryListener>()
private val resolveListeners = HashMap<String, NsdManager.ResolveListener>()
private val registrationListeners = HashMap<String, NsdManager.RegistrationListener>()

private val resolveSemaphore = Semaphore(1)

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
nsdManager =
getSystemService(flutterPluginBinding.applicationContext, NsdManager::class.java)!!
wifiManager =
getSystemService(flutterPluginBinding.applicationContext, WifiManager::class.java)!!
val context = flutterPluginBinding.applicationContext

nsdManager = getSystemService(context, NsdManager::class.java)!!
wifiManager = getSystemService(context, WifiManager::class.java)!!

multicastLock = wifiManager.createMulticastLock("nsdMulticastLock")
multicastLock.setReferenceCounted(true)
if (multicastPermissionGranted(context)) {
multicastLock = wifiManager.createMulticastLock("nsdMulticastLock")
multicastLock?.setReferenceCounted(true)
}

methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
methodChannel.setMethodCallHandler(this)
Expand Down Expand Up @@ -79,7 +85,14 @@ class NsdAndroidPlugin : FlutterPlugin, MethodCallHandler {
"Cannot start discovery: expected handle"
)

multicastLock.acquire()
if (multicastLock == null) {
throw NsdError(
ErrorCause.SECURITY_ISSUE,
"Missing required permission CHANGE_WIFI_MULTICAST_STATE"
);
}

multicastLock?.acquire()

try {

Expand All @@ -95,7 +108,7 @@ class NsdAndroidPlugin : FlutterPlugin, MethodCallHandler {
result.success(null)

} catch (e: Throwable) {
multicastLock.release()
multicastLock?.release()
throw e
}

Expand All @@ -107,7 +120,14 @@ class NsdAndroidPlugin : FlutterPlugin, MethodCallHandler {
"Cannot stop discovery: expected handle"
)

multicastLock.release()
if (multicastLock == null) {
throw NsdError(
ErrorCause.SECURITY_ISSUE,
"Missing required permission CHANGE_WIFI_MULTICAST_STATE"
);
}

multicastLock?.release()

nsdManager.stopServiceDiscovery(discoveryListeners[handle])
result.success(null)
Expand Down Expand Up @@ -290,4 +310,10 @@ class NsdAndroidPlugin : FlutterPlugin, MethodCallHandler {
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel.setMethodCallHandler(null)
}

private fun multicastPermissionGranted(context: Context) =
ContextCompat.checkSelfPermission(
context,
Manifest.permission.CHANGE_WIFI_MULTICAST_STATE
) == PackageManager.PERMISSION_GRANTED
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ internal enum class ErrorCause(val code: String) {
ALREADY_ACTIVE("alreadyActive"),
MAX_LIMIT("maxLimit"),
INTERNAL_ERROR("internalError"),
SECURITY_ISSUE("securityIssue"),
}

internal class NsdError(val errorCause: ErrorCause, val errorMessage: String) :
Expand Down
20 changes: 19 additions & 1 deletion nsd_platform_interface/lib/src/method_channel_nsd_platform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:nsd_platform_interface/src/utilities.dart';
import 'package:uuid/uuid.dart';

import 'logging.dart';
Expand Down Expand Up @@ -195,7 +196,9 @@ class MethodChannelNsdPlatform extends NsdPlatformInterface {

Future<void> invoke(String method, [dynamic arguments]) {
log(this, LogTopic.calls, () => 'Call: $method $arguments');
return _methodChannel.invokeMethod(method, arguments);
return _methodChannel
.invokeMethod(method, arguments)
.catchError((e) => throw toNsdError(e));
}

void setHandler(String handle, String method, _Handler handler) {
Expand Down Expand Up @@ -268,3 +271,18 @@ InternetAddressType? getInternetAddressType(IpLookupType ipLookupType) {
bool isIpLookupEnabled(IpLookupType ipLookupType) {
return ipLookupType != IpLookupType.none;
}

NsdError toNsdError(Exception e) {
if (e is! PlatformException) {
return NsdError(ErrorCause.internalError, e.toString());
}

final message = e.message ?? '';
final errorCode = enumValueFromString(ErrorCause.values, e.code);

if (errorCode == null) {
return NsdError(ErrorCause.internalError, message);
}

return NsdError(errorCode, message);
}
9 changes: 6 additions & 3 deletions nsd_platform_interface/lib/src/nsd_platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,20 @@ enum ErrorCause {
maxLimit,

/// This error occurs on Android (seen on API 30) if too many resolve
/// operations are requested simultanously. It should be prevented by the
/// operations are requested simultaneously. It should be prevented by the
/// semaphore on the native side.
alreadyActive,

/// An error in platform or native code that cannot be adressed by the client.
/// An error in platform or native code that cannot be addressed by the client.
internalError,

/// A security issue, for example a missing permission.
securityIssue,
}

/// Represents an error that occurred during an NSD operation.
///
/// Examine the [ErrorCause] to see wether or not the error can be adressed by the client.
/// Examine the [ErrorCause] to see whether or not the error can be addressed by the client.
class NsdError extends Error {
final ErrorCause cause;
final String message;
Expand Down

0 comments on commit 65d9e7e

Please sign in to comment.