Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
sultanmyrza opened this issue Aug 11, 2021 · 25 comments

Comments

@sultanmyrza
Copy link

sultanmyrza commented Aug 11, 2021

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
<dict>
    <key>com.apple.developer.networking.HotspotConfiguration</key>
    <true/>
    <key>com.apple.developer.networking.networkextension</key>
    <array>
  	  <string>app-proxy-provider</string>
    </array>
    <key>com.apple.developer.networking.wifi-info</key>
    <true/>
    <key>com.apple.external-accessory.wireless-configuration</key>
    <true/>
</dict>

My Info.plist

click to expand
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>goproexperiments</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>$(FLUTTER_BUILD_NAME)</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>$(FLUTTER_BUILD_NUMBER)</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>Need BLE permission</string>
    <key>NSBluetoothPeripheralUsageDescription</key>
    <string>Need BLE permission</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>Need Location permission</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>Need Location permission</string>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>Need Location permission</string>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIMainStoryboardFile</key>
    <string>Main</string>
    <key>UISupportedInterfaceOrientations</key>
    <array>
  	  <string>UIInterfaceOrientationPortrait</string>
  	  <string>UIInterfaceOrientationLandscapeLeft</string>
  	  <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
  	  <string>UIInterfaceOrientationPortrait</string>
  	  <string>UIInterfaceOrientationPortraitUpsideDown</string>
  	  <string>UIInterfaceOrientationLandscapeLeft</string>
  	  <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UIViewControllerBasedStatusBarAppearance</key>
    <false/>
</dict>
</plist>
@Girani
Copy link

Girani commented Aug 27, 2021

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
Copy link
Member

daadu commented Aug 28, 2021

Related issue: #163

@daadu
Copy link
Member

daadu commented Sep 19, 2021

the fix for getSSID is done at #175.

@DominikStarke Does the PR fix this issue too?

@DominikStarke
Copy link
Contributor

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
Copy link
Member

daadu commented Sep 19, 2021

@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
Copy link

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

    1. application is using CoreLocation API and has user's authorization to access precise location.
    1. application has used NEHotspotConfiguration API to configure the current Wi-Fi network.
    1. application has active VPN configurations installed.
    1. application has active NEDNSSettingsManager configuration installed.

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
Copy link
Contributor

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
Copy link
Member

daadu commented Nov 26, 2021

[] 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
Copy link
Member

daadu commented Nov 26, 2021

Some references

  • Apple Docs for apply(_:completionHandler:)

    The system calls your completion handler when it has applied the Wi-Fi level configuration. A successful configuration doesnʼt mean the device has joined that Wi-Fi network. To get the current Wi-Fi state, call fetchCurrent(completionHandler:).

@DominikStarke
Copy link
Contributor

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
Copy link
Member

daadu commented Nov 26, 2021

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
Copy link
Member

daadu commented Nov 26, 2021

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
Copy link
Member

daadu commented Nov 26, 2021

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
Copy link
Member

daadu commented Nov 26, 2021

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

@liamjbarry
Copy link

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
Copy link
Member

daadu commented Mar 24, 2022

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

@MATTYGILO
Copy link

Any progress on this issue?

@Joshfindit
Copy link

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)

  • All 'browser' based applications will freeze, crash, or simply not be able to fill the UI with network requests. ('Browser' in this case meaning applications that I suspect of using WebKit or WebKit-like views)
  • The device will stop showing up over wifi (not visible in Finder, iMazing, or Xcode)
  • Other applications will behave strangely

On the other side of this:

  • Using other apps I can ping from the iOS device out to both IP addresses (192.*, 1.1.1.1, etc) and domain names (www.google.com, etc)
  • I can ssh in to the device successfully even while a 'crashed' application is on the screen

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
Copy link
Member

daadu commented Jun 21, 2022

@Joshfindit Feel free to shed light on this issue.

@liamjbarry
Copy link

@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
Copy link
Member

daadu commented Jul 26, 2022

@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
Copy link

  • 1

@darhaywa
Copy link
Contributor

darhaywa commented Nov 16, 2022

@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
Copy link
Member

daadu commented Nov 18, 2022

@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
Copy link
Member

daadu commented Nov 18, 2022

  • I would prefer "error-result" than throwing/raising error as a design choice for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

11 participants