NEHotspotHelperでAuthenticatingにフックする

NEHotspotHelperの情報は、開発に使用するのにも申請が必須というハードルの高さからか情報が少ない。 そんな中で貴重な情報源の次のページを参考にNEHotspotHelperを使ってみるとはまるケースがある。

[iOS] Wi-Fi一覧でアピール表示し、パスワードを自動入力して接続させる方法(とまとレストランの販売促進アプリ) | Developers.IO

はまったケース

f:id:bagpack:20170707104434p:plainApple Inc. online: Authentication State Machine

上図のAuthenticatingMaintainingの処理にフックしたい場合に、参考コードに処理を付け加える形で書くと、kNEHotspotHelperCommandTypeAuthenticateがいつまでも呼ばれない。 というのは、下記のコードをよくよく読んでみると、

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@"🍅🍅🍅とまとレストラン🍅🍅🍅", kNEHotspotHelperOptionDisplayName, nil];
    dispatch_queue_t queue = dispatch_get_main_queue();
    BOOL isAvailable = [NEHotspotHelper registerWithOptions:options queue:queue handler: ^(NEHotspotHelperCommand * cmd) {
        NSMutableArray *hotspotList = [NSMutableArray new];
 
        if(cmd.commandType == kNEHotspotHelperCommandTypeEvaluate || cmd.commandType == kNEHotspotHelperCommandTypeFilterScanList) {
            for (NEHotspotNetwork* network  in cmd.networkList) {
                NSLog(@">%@", network.SSID);
                if ([network.SSID isEqualToString:@"spw05"]) {
                    [network setConfidence:kNEHotspotHelperConfidenceHigh];
                    [network setPassword:@"password"];
                    [hotspotList addObject:network];
                }
            }
            NEHotspotHelperResponse *response = [cmd createResponse:kNEHotspotHelperResultSuccess];
            [response setNetworkList:hotspotList];
            [response deliver];
        }
    }];

[Developers.IO online: [iOS] Wi-Fi一覧でアピール表示し、パスワードを自動入力して接続させる方法(とまとレストランの販売促進アプリ) | Developers.IO]

kNEHotspotHelperCommandTypeEvaluateのときはcmd.networkListにはnilが入ってくるので、setNetworkListで空リストがセットされている。 そのため、対象のNetworkがNEHotspotHelperの管理対象から外れて、kNEHotspotHelperCommandTypeEvaluate以降のkNEHotspotHelperCommandTypeAuthenticateなどのコマンドが送られない状態となっている。

kNEHotspotHelperCommandTypeAuthenticateが呼ばれるために必要なこと

フォーラムの次の回答によると、

NEHotspotHelper (NetworkExtension iOS9.0) Sampl... | Apple Developer Forums

kNEHotspotHelperCommandTypeEvaluateのときに下記の処理をすればAuthenticatingステートが呼ばれるらしい。

  • コマンドからレスポンスを作製する(NEHotspotHelperResponse *response = [cmd createResponse:kNEHotspotHelperResultSuccess])
  • コマンドからnetworkを取得する(NENetwork *network = cmd.network)
  • networkにconfidenceを設定する(setConfidence)
  • レスポンスにnetworkを追加する(setNetwork)
  • レスポンスを配達する( [response deliver])

サンプルコード

以上を踏まえて、次のようなコードを書いてみると(ついでにSwift3に書き換え)、確かにauthenticateが呼ばれるようになった。 よかったです。

        let options : [String: NSString] = [
            kNEHotspotHelperOptionDisplayName: "🍅🍅🍅とまとレストラン🍅🍅🍅"
        ]
        
        let isAvailable = NEHotspotHelper.register(options: options, queue: DispatchQueue.main) { (cmd) in
            
            print(cmd)
            
            switch cmd.commandType {
            case .filterScanList:
                let list = cmd.networkList
                var networks = [NEHotspotNetwork]()
                for network in list! {
                    if network.ssid == "spw05" {
                        network.setPassword("password")
                        network.setConfidence(.high)
                        networks.append(network)
                    }
                }
                let response = cmd.createResponse(.success)
                response.setNetworkList(networks)
                response.deliver()
            case .evaluate:
                let network = cmd.network!
                let response = cmd.createResponse(.success)
                network.setConfidence(.high)
                response.setNetwork(network)
                response.deliver()
            case .authenticate:
                let network = cmd.network!
                let response = cmd.createResponse(.success)
                response.setNetwork(network)
                response.deliver()
            case .maintain:
                let network = cmd.network!
                let response = cmd.createResponse(.success)
                response.setNetwork(network)
                response.deliver()
            case .none:
                // anycode
                break
            case .presentUI:
                // anycode
                break
            case .logoff:
                // anycode
                break
            }
            
        }