flutternetwork / WiFiFlutter

Flutter plugin suite for various WiFi services.
https://wifi.flutternetwork.dev
288 stars 179 forks source link

IOS - Could't connect, [] NEHotspotNetwork nehelper sent invalid result code [1] for Wi-Fi information request #166

Open sultanmyrza opened 3 years ago

sultanmyrza commented 3 years ago

I run example app https://github.com/alternadom/WiFiFlutter/tree/master/example on ios getting this error

NEHotspotNetwork nehelper sent invalid result code [1] for Wi-Fi information request

My environment

xcode version 12.3 (12C33)

Flutter (Channel dev, 2.3.0-24.0.pre, on Mac OS X 10.15.7 19H2 darwin-x64,  locale en)

real device ios 14.6

My Runner.entitlements

Screen Shot 2021-08-11 at 6 36 01 PM

click to expand ``` xml com.apple.developer.networking.HotspotConfiguration com.apple.developer.networking.networkextension app-proxy-provider com.apple.developer.networking.wifi-info com.apple.external-accessory.wireless-configuration ```

My Info.plist

click to expand ``` xml CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName goproexperiments CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS NSBluetoothAlwaysUsageDescription Need BLE permission NSBluetoothPeripheralUsageDescription Need BLE permission NSLocationAlwaysAndWhenInUseUsageDescription Need Location permission NSLocationAlwaysUsageDescription Need Location permission NSLocationWhenInUseUsageDescription Need Location permission UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ```
Girani commented 3 years ago

SwiftWifiIotPlugin method getSSID() can be changed to:

private func getSSID() -> String? {
        var ssid: String?
        if #available(iOS 14.0, *) {
            NEHotspotNetwork.fetchCurrent(completionHandler: { currentNetwork in
                ssid = currentNetwork?.ssid
            })
        }
        if(ssid == nil){
            if let interfaces = CNCopySupportedInterfaces() as NSArray? {
                for interface in interfaces {
                    if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? {
                        ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String
                        break
                    }
                }
            }
        }
        return ssid
    }

In case the NEHotspotNetwork gives the error, the CNCopySupportedInterfaces will do the trick. Tested on iOS 14.7.1

daadu commented 3 years ago

Related issue: #163

daadu commented 3 years ago

the fix for getSSID is done at #175.

@DominikStarke Does the PR fix this issue too?

DominikStarke commented 3 years ago

Maybe!?

I've only ever seen this error if com.apple.developer.networking.HotspotConfiguration isn't properly set.

It requires the provisioning profile to contain the entitlement and can only be unlocked on developer.apple.com. Manually adding it to the entitlement file is not enough.

daadu commented 3 years ago

@sultanmyrza Can you test against PR and let us know if it fixes the issue. Else as @DominikStarke suggested, check the entitlements the profiles on developer.apple.com account.

martinkong0806 commented 3 years ago

Hello, I encountered the same issue as well. I checked the documents and it was said that in order to make NEHotspotNetwork.fetchCurrent() working one of the four requirements must be satisfied, this is written in the NetworkExtentsion file

The following link also mentions the same requirements https://developer.apple.com/forums/thread/679038

I assume the package is trying to fulfil the second requirement, however I didn't see that the NEHotspotConfiguration API is called from the getSSID() function in ios/Classes/SwiftWifiIotPlugin.swift judging from the code. I tried to satisfy the first requirement by : Adding the following variable to SwiftWifiIotPlugin class

var locationManager = CLLocationManager();

and add the following code to the handle()

locationManager.requestWhenInUseAuthorization()

and it worked after the app asked for the device location, hence satisfy the first requirement.

By all means I am not experienced in swift, so I do not understand much how the code works at the moment, but apparently using NEHotspotConfiguration API seems requires the Wi-Fi details where it will mostly be unknown most of the time, so I am not sure how you can use that to get the SSID.

DominikStarke commented 3 years ago

The plugin only fulfills condition 2, if you connected to the wifi using the plugin as well.

Otherwise you'll need the locationPermission (for example using this plugin: https://pub.dev/packages/permission_handler)

daadu commented 2 years ago

[] NEHotspotNetwork nehelper sent invalid result code [1] for Wi-Fi information request is also logged out when calling WiFiForIoTPlugin.connect (Tested with iOS 15).

Turns out that connect tries to fetch current wifi info after connecting - to see if it was connected or not - apparenly the comment (L157) reads this is to check if wifi was found or not - ios does not throw any error for it. Check this part:

https://github.com/alternadom/WiFiFlutter/blob/99f62dda34606effd91de6168b095087270f4e3a/packages/wifi_iot/ios/Classes/SwiftWifiIotPlugin.swift#L140-L164

On my test device - the device gets connected to the wifi but returns with false with folowing output: WiFi network not found

This is the print message from L160 (confirmed it by changing the message).

Looks like getSSID is returning null (at least immediately) even when the network is connected via NEHotspotConfiguration.

@DominikStarke Can you confirm the above findings? And do you have any idea of how to fix it?

daadu commented 2 years ago

Some references

DominikStarke commented 2 years ago

I cannot confirm this right now, because I don't have my macbook available until monday.

It seems we have to check for connectivity before proceeding any further as mentioned in the article:

Furthermore, joining a Wi-Fi network doesn’t guarantee that the network is fully operational. The system might still be in the process of configuring TCP/IP on that network.

The article also mentions that we should call fetchCurrent to retrieve a NEHotspotNetwork, which should hold some information regarding the currently joined network. But in my tests these informations were always null.

daadu commented 2 years ago

I checked by adding delay - it worked but is unreliable - sometimes it too 4s - sometimes 8s, sometimes >10s.

In android in older connectToDeprecated method we used to wait for upto 30s - checking every second.

Something similar needs to be implemented.Maybe.

But when calling connect twice - second time it always works.

daadu commented 2 years ago

This the code I tested with

            NEHotspotConfigurationManager.shared.apply(configuration) { [weak self] (error) in
                guard let this = self else {
                    print("WiFi network not found")
                    result(false)
                    return
                }

                if (error != nil) {
                    if (error?.localizedDescription == "already associated.") {
                        print("Already connected")
                        result(true)
                    } else {
                        print("Not Connected")
                        result(false)
                    }
                }else{
                    var checkCount = 0;
                    let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
                       checkCount += 1
                       this.getSSID { (sSSID) -> () in
                           print("Check if connected after x\(checkCount) seconds...")
                           if sSSID != nil || checkCount > 15 {
                               timer.invalidate()
                               if let ssid = sSSID {
                                  print("Connected to " + ssid)
                                  // ssid check is required because if wifi not found (could not connect) there seems to be no error given
                                  result(ssid == sSSID)
                              } else {
                                  print("WiFi network not found")
                                  result(false)
                              }
                           }
                       }
                    }
                }
            }

As I said is unreliable - sometimes it does not work even after 15s.

@DominikStarke Let me know if any syntactical error - I don't know ios/swift.

daadu commented 2 years ago

I see 3 approaches (from best case to worse case) from here to reliably return the result of connection request:

  1. make getSSID work after NEHotspotConfigurationManager.apply
  2. call NEHotspotConfigurationManager.apply twice - as it definetly works the second time
  3. ask location permission - as this also make sure the getSSID works

Apart from this, we can choose to not fix it and add this as caveats in README.

daadu commented 2 years ago

Posted about it on Apple Forum: https://developer.apple.com/forums/thread/695418

liamjbarry commented 2 years ago

Hi all,

Any movement on this at all?

Android works perfectly but iOS however... More often than not I get NEHotspotNetwork nehelper sent invalid result code [1] for Wi-Fi information request followed by WiFi network not found in my console but I find the iPhone has actually connected. WiFiForIoTPlugin.connect() then returns false causing my error handling to catch it and ask the user to try again, as mentioned previously in this thread calling WiFiForIoTPlugin.connect() a second time returns true

While calling WiFiForIoTPlugin.connect() a second time on iOS does return true it also causes the "join network" iOS native prompt to appear a second time, which isn't a great user experience.

I have tried a few other things in my flutter code but with not much luck, if anyone has any updates or solutions, I would greatly appreciate assistance

Many thanks

daadu commented 2 years ago

@liamjbarry Got 1 response on the Apple Forum thread, will work on it and get back here.

MATTYGILO commented 2 years ago

Any progress on this issue?

Joshfindit commented 2 years ago

I don't know if this is helpful, but just in case:

I get this error after a recent change to a jailbreak device. I'm currently in the process of diagnosing it because it's rather annoying.

Symptoms:

At a seemingly random time after using Unc0ver to jailbreak the device (that is: everything is fine in the first few minutes post-jailbreak)

On the other side of this:

I arrived at this thread because while replicating the issue in Instagram I got this:

error   09:47:46.839605-0300    Instagram   Couldn't read values in CFPrefsPlistSource<0x281b9f600> (Domain: group.com.burbn.instagram, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd
error   09:47:46.858078-0300    Instagram   nehelper sent invalid result code [1] for Wi-Fi information request
error   09:47:46.861257-0300    nehelper    WiFiManagerClientGetDevice: null deviceClient for interface <private> (manager <private>)
error   09:47:46.861340-0300    nehelper    [NEHelperWiFiInfoManager] WiFiManagerClientGetDevice for en1 returned NULL
error   09:47:46.862843-0300    nehelper    WiFiManagerClientGetDevice: null deviceClient for interface <private> (manager <private>)
error   09:47:46.862972-0300    nehelper    [NEHelperWiFiInfoManager] WiFiManagerClientGetDevice for en2 returned NULL
error   09:47:46.865622-0300    CommCenter  Client [<private>] entitlement failed: 'public-signal-strength', required for request "<private>"

Note the second line

Apologies if this pollutes the thread. I am hoping that it adds information that helps unblock others. If I get the go-ahead I will try to loop back on this when I find the solution for my specific device.

daadu commented 2 years ago

@Joshfindit Feel free to shed light on this issue.

liamjbarry commented 2 years ago

@daadu So I had some time to do some digging and I have come up with the following...

The example given by Apple shows confirming the connected SSID in a separate screen/process, not being called automatically after a successful NEHotspotConfigurationManager.shared.apply so I first commented out the line to automatically get the SSID, moved this code to a new function in the Swift file and setup all the other code so I could call this function from Flutter (I know I could have used the getSSID() function that already existed I just wanted my own that I could modify and break etc)

Flutter code in the example app now looks like

Future<bool> connect() async {
    try {
      var isApplied = await WiFiForIoTPlugin.connect(ssid,
          password: pass,
          security: NetworkSecurity.WPA,
          withInternet: false,
          joinOnce: true);
      print("wifi applied $isApplied");
      var currentSSID = await WiFiForIoTPlugin.currentSSID(); //this is the function I added
      print(currentSSID);
      return isConnected;
    } on Exception catch (e) {
      print(e.toString());
      return false;
    }
  }

Swift code for my currentSSID() function

private func getCurrentSSID(result: FlutterResult) {
        var count = 0;
        if #available(iOS 14.0, *) {
            Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { timer in
                if (count == 8) {
                    timer.invalidate()
                }
                NEHotspotNetwork.fetchCurrent(completionHandler: { (network) in
                    if let unwrappedNetwork = network {
                        let networkSSID = unwrappedNetwork.ssid
                        print("Network: \(networkSSID) and signal strength \(unwrappedNetwork.signalStrength)")
                        timer.invalidate()
                    } else {
                        print("No available network")
                    }
                })
                count += 1
                print("[DEBUG] - Number: \(count)")
            }
            }
    }

I have very little knowledge of swift so this was all a bit of a stab in the dark, I managed to steal bits of this from various forum posts, this seemed to work it would successfully log the correct network name to console and reliably work too. I had the timer in from previous testing but I imagine this could be removed. I was getting the correct result printed to console so now I just needed to get the SSID back to Flutter so I could confirm it was connected to the network I requested in Flutter. After reading some of the other code in the wifi_iot library I tried adding an @escaping tag to return the value and suddenly I was getting the dreaded [] NEHotspotNetwork nehelper sent invalid result code [1] for Wi-Fi information request again, I removed the @escaping (reverted to Swift code above) and it worked reliably again.

Swift code with @escaping

    private func getCurrentSSID(result: @escaping FlutterResult) {
        var count = 0;
        if #available(iOS 14.0, *) {
            Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { timer in
                if (count == 8) {
                    timer.invalidate()
                }
                NEHotspotNetwork.fetchCurrent(completionHandler: { (network) in
                    if let unwrappedNetwork = network {
                        let networkSSID = unwrappedNetwork.ssid
                        print("Network: \(networkSSID) and signal strength \(unwrappedNetwork.signalStrength)")
                        timer.invalidate()
                        result(networkSSID);
                    } else {
                        print("No available network")
                    }
                })
                count += 1
                print("[DEBUG] - Number: \(count)")
            }
            }
    }

So the TLDR of this is I believe it’s some sort of async/await problem? As I said I am unfamiliar with Swift so I may well be doing it wrong but from my testing as soon as I added the @escaping it would stop working reliably.

All of this code is incomplete as I was just trying to get to the bottom of what the issue was and I hope I made some headway on this issue or at least understanding it. I am unsure as to what a solution might be as I have little knowledge of how Flutter interacts with Swift code and Swift itself. from my testing it seems that applying the configuration and checking the currently connected network need to be called separately, again I am unsure as to why this is but it seems to work reliably when you do that.

I apologise for the waffle but I had a real roller-coaster of a day with all this testing on Friday 😆 If anyone has any ideas or suggestions as to what to try next please feel free

daadu commented 2 years ago

@liamjbarry Thanks for the effort and sharing it. I am not good with Swift either, a lot of the swift code was written by someone else.

@DominikStarke Can you check the above comment? any idea on what makes this work? how can we fix the issue by integrating this?

kekko7072 commented 2 years ago
darhaywa commented 1 year ago

@daadu, came across problem #271 getSSID returns nil for iOS 15 and above. It led here to this defect, however, I don't think it's related as we see this error but the connection still works.

Tried with a loop(20 times 1 sec interval) calling the isConnected() API to perform the NEHotspotNetwork.fetchCurrent() call and when it fails it's still failing. Tried calling the connect() API after failure and it returns within 4s as connected, whereas it previously took 6-8s on a successful connection, the downside is this can cause a duplicate OS request to join the network.

The problem with calling the API twice is that we can't determine that the user has not clicked cancel, only a true/false is returned for all paths.

There is a possibility to return an error in this scenario, but this is not the current behaviour of the plugin, and I don't want to provide a solution that is unacceptable. Would it be ok to return an error when the user denies access that can be detected by the user of the wifi_iot and catch it in a try-catch block?

daadu commented 1 year ago

@darhaywa You could do that, and would be considered breaking change - plus, need to implement similar behavior with Android.

Lately, I have not been able to work on this - but the real solution here is #229. In wifi_connect_to plugin [#189] - we can plan proper error-results. But sadly, I am not able to give time to it.

daadu commented 1 year ago