1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi.hotspot2;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.net.Network;
26 import android.net.wifi.ScanResult;
27 import android.net.wifi.WifiManager;
28 import android.net.wifi.WifiSsid;
29 import android.net.wifi.hotspot2.IProvisioningCallback;
30 import android.net.wifi.hotspot2.OsuProvider;
31 import android.net.wifi.hotspot2.PasspointConfiguration;
32 import android.net.wifi.hotspot2.ProvisioningCallback;
33 import android.net.wifi.hotspot2.omadm.PpsMoParser;
34 import android.net.wifi.util.Environment;
35 import android.os.Handler;
36 import android.os.HandlerThread;
37 import android.os.Looper;
38 import android.os.RemoteException;
39 import android.os.UserHandle;
40 import android.util.Log;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.server.wifi.WifiMetrics;
44 import com.android.server.wifi.WifiNative;
45 import com.android.server.wifi.hotspot2.anqp.ANQPElement;
46 import com.android.server.wifi.hotspot2.anqp.Constants;
47 import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement;
48 import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo;
49 import com.android.server.wifi.hotspot2.soap.ExchangeCompleteMessage;
50 import com.android.server.wifi.hotspot2.soap.PostDevDataMessage;
51 import com.android.server.wifi.hotspot2.soap.PostDevDataResponse;
52 import com.android.server.wifi.hotspot2.soap.RedirectListener;
53 import com.android.server.wifi.hotspot2.soap.SppConstants;
54 import com.android.server.wifi.hotspot2.soap.SppResponseMessage;
55 import com.android.server.wifi.hotspot2.soap.UpdateResponseMessage;
56 import com.android.server.wifi.hotspot2.soap.command.BrowserUri;
57 import com.android.server.wifi.hotspot2.soap.command.PpsMoData;
58 import com.android.server.wifi.hotspot2.soap.command.SppCommand;
59 
60 import java.net.MalformedURLException;
61 import java.net.URL;
62 import java.security.cert.X509Certificate;
63 import java.util.HashMap;
64 import java.util.List;
65 import java.util.Locale;
66 import java.util.Map;
67 import java.util.stream.Collectors;
68 
69 /**
70  * Provides methods to carry out provisioning flow
71  */
72 public class PasspointProvisioner {
73     private static final String TAG = "PasspointProvisioner";
74 
75     // Indicates callback type for caller initiating provisioning
76     private static final int PROVISIONING_STATUS = 0;
77     private static final int PROVISIONING_FAILURE = 1;
78 
79     // TLS version to be used for HTTPS connection with OSU server
80     private static final String TLS_VERSION = "TLS";
81 
82     private final Context mContext;
83     private final ProvisioningStateMachine mProvisioningStateMachine;
84     private final OsuNetworkCallbacks mOsuNetworkCallbacks;
85     private final OsuNetworkConnection mOsuNetworkConnection;
86     private final OsuServerConnection mOsuServerConnection;
87     private final WfaKeyStore mWfaKeyStore;
88     private final PasspointObjectFactory mObjectFactory;
89     private final SystemInfo mSystemInfo;
90     private int mCurrentSessionId = 0;
91     private int mCallingUid;
92     private boolean mVerboseLoggingEnabled = false;
93     private WifiManager mWifiManager;
94     private PasspointManager mPasspointManager;
95     private Looper mLooper;
96     private final WifiMetrics mWifiMetrics;
97 
98     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
PasspointProvisioner(Context context, WifiNative wifiNative, PasspointObjectFactory objectFactory, PasspointManager passpointManager, WifiMetrics wifiMetrics)99     public PasspointProvisioner(Context context, WifiNative wifiNative,
100             PasspointObjectFactory objectFactory, PasspointManager passpointManager,
101             WifiMetrics wifiMetrics) {
102         mContext = context;
103         mOsuNetworkConnection = objectFactory.makeOsuNetworkConnection(context);
104         mProvisioningStateMachine = new ProvisioningStateMachine();
105         mOsuNetworkCallbacks = new OsuNetworkCallbacks();
106         mOsuServerConnection = objectFactory.makeOsuServerConnection();
107         mWfaKeyStore = objectFactory.makeWfaKeyStore();
108         mSystemInfo = objectFactory.getSystemInfo(context, wifiNative);
109         mObjectFactory = objectFactory;
110         mPasspointManager = passpointManager;
111         mWifiMetrics = wifiMetrics;
112     }
113 
114     /**
115      * Sets up for provisioning
116      *
117      * @param looper Looper on which the Provisioning state machine will run
118      */
init(Looper looper)119     public void init(Looper looper) {
120         mLooper = looper;
121         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
122         mProvisioningStateMachine.start(new Handler(mLooper));
123         mOsuNetworkConnection.init(mProvisioningStateMachine.getHandler());
124         // Offload the heavy load job to another thread
125         mProvisioningStateMachine.getHandler().post(() -> {
126             mWfaKeyStore.load();
127             mOsuServerConnection.init(mObjectFactory.getSSLContext(TLS_VERSION),
128                     mObjectFactory.getTrustManagerFactory(mWfaKeyStore.get()));
129         });
130     }
131 
132     /**
133      * Enable verbose logging to help debug failures
134      *
135      * @param verbose enables verbose logging.
136      */
enableVerboseLogging(boolean verbose)137     public void enableVerboseLogging(boolean verbose) {
138         mVerboseLoggingEnabled = verbose;
139         mOsuNetworkConnection.enableVerboseLogging(verbose);
140         mOsuServerConnection.enableVerboseLogging(verbose);
141     }
142 
143     /**
144      * Start provisioning flow with a given provider.
145      *
146      * @param callingUid calling uid.
147      * @param provider   {@link OsuProvider} to provision with.
148      * @param callback   {@link IProvisioningCallback} to provide provisioning status.
149      * @return boolean value, true if provisioning was started, false otherwise.
150      *
151      * Implements HS2.0 provisioning flow with a given HS2.0 provider.
152      */
startSubscriptionProvisioning(int callingUid, OsuProvider provider, IProvisioningCallback callback)153     public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
154             IProvisioningCallback callback) {
155         mCallingUid = callingUid;
156 
157         Log.v(TAG, "Provisioning started with " + provider.toString());
158 
159         mProvisioningStateMachine.getHandler().post(() -> {
160             mProvisioningStateMachine.startProvisioning(provider, callback);
161         });
162 
163         return true;
164     }
165 
166     /**
167      * Handles the provisioning flow state transitions
168      */
169     class ProvisioningStateMachine {
170         private static final String TAG = "PasspointProvisioningStateMachine";
171 
172         static final int STATE_INIT = 1;
173         static final int STATE_AP_CONNECTING = 2;
174         static final int STATE_OSU_SERVER_CONNECTING = 3;
175         static final int STATE_WAITING_FOR_FIRST_SOAP_RESPONSE = 4;
176         static final int STATE_WAITING_FOR_REDIRECT_RESPONSE = 5;
177         static final int STATE_WAITING_FOR_SECOND_SOAP_RESPONSE = 6;
178         static final int STATE_WAITING_FOR_THIRD_SOAP_RESPONSE = 7;
179         static final int STATE_WAITING_FOR_TRUST_ROOT_CERTS = 8;
180 
181         private OsuProvider mOsuProvider;
182         private IProvisioningCallback mProvisioningCallback;
183         private int mState = STATE_INIT;
184         private Handler mHandler;
185         private URL mServerUrl;
186         private Network mNetwork;
187         private String mSessionId;
188         private String mWebUrl;
189         private PasspointConfiguration mPasspointConfiguration;
190         private RedirectListener mRedirectListener;
191         private HandlerThread mRedirectHandlerThread;
192         private Handler mRedirectStartStopHandler;
193 
194         /**
195          * Initializes and starts the state machine with a handler to handle incoming events
196          */
start(Handler handler)197         public void start(Handler handler) {
198             mHandler = handler;
199             if (mRedirectHandlerThread == null) {
200                 mRedirectHandlerThread = new HandlerThread("RedirectListenerHandler");
201                 mRedirectHandlerThread.start();
202                 mRedirectStartStopHandler = new Handler(mRedirectHandlerThread.getLooper());
203             }
204         }
205 
206         /**
207          * Returns the handler on which a runnable can be posted
208          *
209          * @return Handler State Machine's handler
210          */
getHandler()211         public Handler getHandler() {
212             return mHandler;
213         }
214 
215         /**
216          * Start Provisioning with the Osuprovider and invoke callbacks
217          *
218          * @param provider OsuProvider to provision with
219          * @param callback IProvisioningCallback to invoke callbacks on
220          * Note: Called on main thread (WifiService thread).
221          */
startProvisioning(OsuProvider provider, IProvisioningCallback callback)222         public void startProvisioning(OsuProvider provider, IProvisioningCallback callback) {
223             if (mVerboseLoggingEnabled) {
224                 Log.v(TAG, "startProvisioning received in state=" + mState);
225             }
226 
227             if (mState != STATE_INIT) {
228                 if (mVerboseLoggingEnabled) {
229                     Log.v(TAG, "State Machine needs to be reset before starting provisioning");
230                 }
231                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
232             }
233             mProvisioningCallback = callback;
234             mRedirectListener = RedirectListener.createInstance(mLooper);
235 
236             if (mRedirectListener == null) {
237                 resetStateMachineForFailure(
238                         ProvisioningCallback.OSU_FAILURE_START_REDIRECT_LISTENER);
239                 return;
240             }
241 
242             if (!mOsuServerConnection.canValidateServer()) {
243                 Log.w(TAG, "Provisioning is not possible");
244                 resetStateMachineForFailure(
245                         ProvisioningCallback.OSU_FAILURE_PROVISIONING_NOT_AVAILABLE);
246                 return;
247             }
248             URL serverUrl;
249             try {
250                 serverUrl = new URL(provider.getServerUri().toString());
251             } catch (MalformedURLException e) {
252                 Log.e(TAG, "Invalid Server URL");
253                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_URL_INVALID);
254                 return;
255             }
256             mServerUrl = serverUrl;
257             mOsuProvider = provider;
258             if (mOsuProvider.getOsuSsid() == null) {
259                 // Find a best matching OsuProvider that has an OSU SSID from current scanResults
260                 List<ScanResult> scanResults = mWifiManager.getScanResults();
261                 mOsuProvider = getBestMatchingOsuProvider(scanResults, mOsuProvider);
262                 if (mOsuProvider == null) {
263                     resetStateMachineForFailure(
264                             ProvisioningCallback.OSU_FAILURE_OSU_PROVIDER_NOT_FOUND);
265                     return;
266                 }
267             }
268 
269             // Register for network and wifi state events during provisioning flow
270             mOsuNetworkConnection.setEventCallback(mOsuNetworkCallbacks);
271 
272             // Register for OSU server callbacks
273             mOsuServerConnection.setEventCallback(new OsuServerCallbacks(++mCurrentSessionId));
274 
275             if (!mOsuNetworkConnection.connect(mOsuProvider.getOsuSsid(),
276                     mOsuProvider.getNetworkAccessIdentifier(), mOsuProvider.getFriendlyName())) {
277                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
278                 return;
279             }
280             invokeProvisioningCallback(PROVISIONING_STATUS,
281                     ProvisioningCallback.OSU_STATUS_AP_CONNECTING);
282             changeState(STATE_AP_CONNECTING);
283         }
284 
285         /**
286          * Handles Wifi Disable event
287          *
288          * Note: Called on main thread (WifiService thread).
289          */
handleWifiDisabled()290         public void handleWifiDisabled() {
291             if (mVerboseLoggingEnabled) {
292                 Log.v(TAG, "Wifi Disabled in state=" + mState);
293             }
294             if (mState == STATE_INIT) {
295                 Log.w(TAG, "Wifi Disable unhandled in state=" + mState);
296                 return;
297             }
298             resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
299         }
300 
301         /**
302          * Handles server connection status
303          *
304          * @param sessionId indicating current session ID
305          * @param succeeded boolean indicating success/failure of server connection
306          * Note: Called on main thread (WifiService thread).
307          */
handleServerConnectionStatus(int sessionId, boolean succeeded)308         public void handleServerConnectionStatus(int sessionId, boolean succeeded) {
309             if (mVerboseLoggingEnabled) {
310                 Log.v(TAG, "Server Connection status received in " + mState);
311             }
312             if (sessionId != mCurrentSessionId) {
313                 Log.w(TAG, "Expected server connection failure callback for currentSessionId="
314                         + mCurrentSessionId);
315                 return;
316             }
317             if (mState != STATE_OSU_SERVER_CONNECTING) {
318                 Log.wtf(TAG, "Server Validation Failure unhandled in mState=" + mState);
319                 return;
320             }
321             if (!succeeded) {
322                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
323                 return;
324             }
325             invokeProvisioningCallback(PROVISIONING_STATUS,
326                     ProvisioningCallback.OSU_STATUS_SERVER_CONNECTED);
327             mProvisioningStateMachine.getHandler().post(() -> initSoapExchange());
328         }
329 
330         /**
331          * Handles server validation failure
332          *
333          * @param sessionId indicating current session ID
334          * Note: Called on main thread (WifiService thread).
335          */
handleServerValidationFailure(int sessionId)336         public void handleServerValidationFailure(int sessionId) {
337             if (mVerboseLoggingEnabled) {
338                 Log.v(TAG, "Server Validation failure received in " + mState);
339             }
340             if (sessionId != mCurrentSessionId) {
341                 Log.w(TAG, "Expected server validation callback for currentSessionId="
342                         + mCurrentSessionId);
343                 return;
344             }
345             if (mState != STATE_OSU_SERVER_CONNECTING) {
346                 Log.wtf(TAG, "Server Validation Failure unhandled in mState=" + mState);
347                 return;
348             }
349             resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_VALIDATION);
350         }
351 
352         /**
353          * Handles status of server validation success
354          *
355          * @param sessionId indicating current session ID
356          * Note: Called on main thread (WifiService thread).
357          */
handleServerValidationSuccess(int sessionId)358         public void handleServerValidationSuccess(int sessionId) {
359             if (mVerboseLoggingEnabled) {
360                 Log.v(TAG, "Server Validation Success received in " + mState);
361             }
362             if (sessionId != mCurrentSessionId) {
363                 Log.w(TAG, "Expected server validation callback for currentSessionId="
364                         + mCurrentSessionId);
365                 return;
366             }
367             if (mState != STATE_OSU_SERVER_CONNECTING) {
368                 Log.wtf(TAG, "Server validation success event unhandled in state=" + mState);
369                 return;
370             }
371             if (!mOsuServerConnection.validateProvider(
372                     mOsuProvider.getFriendlyNameList())) {
373                 Log.e(TAG,
374                         "OSU Server certificate does not have the one matched with the selected "
375                                 + "Service Name: "
376                                 + mOsuProvider.getFriendlyName());
377                 resetStateMachineForFailure(
378                         ProvisioningCallback.OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION);
379                 return;
380             }
381             invokeProvisioningCallback(PROVISIONING_STATUS,
382                     ProvisioningCallback.OSU_STATUS_SERVER_VALIDATED);
383         }
384 
385         /**
386          * Handles next step once receiving a HTTP redirect response.
387          *
388          * Note: Called on main thread (WifiService thread).
389          */
handleRedirectResponse()390         public void handleRedirectResponse() {
391             if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
392                 Log.e(TAG, "Received redirect request in wrong state=" + mState);
393                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
394                 return;
395             }
396 
397             invokeProvisioningCallback(PROVISIONING_STATUS,
398                     ProvisioningCallback.OSU_STATUS_REDIRECT_RESPONSE_RECEIVED);
399             mRedirectListener.stopServer(mRedirectStartStopHandler);
400             secondSoapExchange();
401         }
402 
403         /**
404          * Handles next step when timeout occurs because {@link RedirectListener} doesn't
405          * receive a HTTP redirect response.
406          *
407          * Note: Called on main thread (WifiService thread).
408          */
handleTimeOutForRedirectResponse()409         public void handleTimeOutForRedirectResponse() {
410             Log.e(TAG, "Timed out for HTTP redirect response");
411 
412             if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
413                 Log.e(TAG, "Received timeout error for HTTP redirect response  in wrong state="
414                         + mState);
415                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
416                 return;
417             }
418             mRedirectListener.stopServer(mRedirectStartStopHandler);
419             resetStateMachineForFailure(
420                     ProvisioningCallback.OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER);
421         }
422 
423         /**
424          * Connected event received
425          *
426          * @param network Network object for this connection
427          * Note: Called on main thread (WifiService thread).
428          */
handleConnectedEvent(Network network)429         public void handleConnectedEvent(Network network) {
430             if (mVerboseLoggingEnabled) {
431                 Log.v(TAG, "Connected event received in state=" + mState);
432             }
433             if (mState != STATE_AP_CONNECTING) {
434                 // Not waiting for a connection
435                 Log.wtf(TAG, "Connection event unhandled in state=" + mState);
436                 return;
437             }
438             invokeProvisioningCallback(PROVISIONING_STATUS,
439                     ProvisioningCallback.OSU_STATUS_AP_CONNECTED);
440             initiateServerConnection(network);
441         }
442 
443         /**
444          * Handles SOAP message response sent by server
445          *
446          * @param sessionId       indicating current session ID
447          * @param responseMessage SOAP SPP response, or {@code null} in any failure.
448          * Note: Called on main thread (WifiService thread).
449          */
handleSoapMessageResponse(int sessionId, @Nullable SppResponseMessage responseMessage)450         public void handleSoapMessageResponse(int sessionId,
451                 @Nullable SppResponseMessage responseMessage) {
452             if (sessionId != mCurrentSessionId) {
453                 Log.w(TAG, "Expected soapMessageResponse callback for currentSessionId="
454                         + mCurrentSessionId);
455                 return;
456             }
457 
458             if (responseMessage == null) {
459                 Log.e(TAG, "failed to send the sppPostDevData message");
460                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
461                 return;
462             }
463 
464             if (mState == STATE_WAITING_FOR_FIRST_SOAP_RESPONSE) {
465                 if (responseMessage.getMessageType()
466                         != SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) {
467                     Log.e(TAG, "Expected a PostDevDataResponse, but got "
468                             + responseMessage.getMessageType());
469                     resetStateMachineForFailure(
470                             ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
471                     return;
472                 }
473 
474                 PostDevDataResponse devDataResponse = (PostDevDataResponse) responseMessage;
475                 mSessionId = devDataResponse.getSessionID();
476                 if (devDataResponse.getSppCommand().getExecCommandId()
477                         != SppCommand.ExecCommandId.BROWSER) {
478                     Log.e(TAG, "Expected a launchBrowser command, but got "
479                             + devDataResponse.getSppCommand().getExecCommandId());
480                     resetStateMachineForFailure(
481                             ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
482                     return;
483                 }
484 
485                 Log.d(TAG, "Exec: " + devDataResponse.getSppCommand().getExecCommandId() + ", for '"
486                         + devDataResponse.getSppCommand().getCommandData() + "'");
487 
488                 mWebUrl = ((BrowserUri) devDataResponse.getSppCommand().getCommandData()).getUri();
489                 if (mWebUrl == null) {
490                     Log.e(TAG, "No Web-Url");
491                     resetStateMachineForFailure(
492                             ProvisioningCallback.OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU);
493                     return;
494                 }
495 
496                 if (!mWebUrl.toLowerCase(Locale.US).contains(mSessionId.toLowerCase(Locale.US))) {
497                     Log.e(TAG, "Bad or Missing session ID in webUrl");
498                     resetStateMachineForFailure(
499                             ProvisioningCallback.OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU);
500                     return;
501                 }
502                 launchOsuWebView();
503             } else if (mState == STATE_WAITING_FOR_SECOND_SOAP_RESPONSE) {
504                 if (responseMessage.getMessageType()
505                         != SppResponseMessage.MessageType.POST_DEV_DATA_RESPONSE) {
506                     Log.e(TAG, "Expected a PostDevDataResponse, but got "
507                             + responseMessage.getMessageType());
508                     resetStateMachineForFailure(
509                             ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
510                     return;
511                 }
512 
513                 PostDevDataResponse devDataResponse = (PostDevDataResponse) responseMessage;
514                 if (devDataResponse.getSppCommand() == null
515                         || devDataResponse.getSppCommand().getSppCommandId()
516                         != SppCommand.CommandId.ADD_MO) {
517                     Log.e(TAG, "Expected a ADD_MO command, but got " + (
518                             (devDataResponse.getSppCommand() == null) ? "null"
519                                     : devDataResponse.getSppCommand().getSppCommandId()));
520                     resetStateMachineForFailure(
521                             ProvisioningCallback.OSU_FAILURE_UNEXPECTED_COMMAND_TYPE);
522                     return;
523                 }
524 
525                 mPasspointConfiguration = buildPasspointConfiguration(
526                         (PpsMoData) devDataResponse.getSppCommand().getCommandData());
527                 thirdSoapExchange(mPasspointConfiguration == null);
528             } else if (mState == STATE_WAITING_FOR_THIRD_SOAP_RESPONSE) {
529                 if (responseMessage.getMessageType()
530                         != SppResponseMessage.MessageType.EXCHANGE_COMPLETE) {
531                     Log.e(TAG, "Expected a ExchangeCompleteMessage, but got "
532                             + responseMessage.getMessageType());
533                     resetStateMachineForFailure(
534                             ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE);
535                     return;
536                 }
537 
538                 ExchangeCompleteMessage exchangeCompleteMessage =
539                         (ExchangeCompleteMessage) responseMessage;
540                 if (exchangeCompleteMessage.getStatus()
541                         != SppConstants.SppStatus.EXCHANGE_COMPLETE) {
542                     Log.e(TAG, "Expected a ExchangeCompleteMessage Status, but got "
543                             + exchangeCompleteMessage.getStatus());
544                     resetStateMachineForFailure(
545                             ProvisioningCallback.OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS);
546                     return;
547                 }
548 
549                 if (exchangeCompleteMessage.getError() != SppConstants.INVALID_SPP_CONSTANT) {
550                     Log.e(TAG,
551                             "In the SppExchangeComplete, got error "
552                                     + exchangeCompleteMessage.getError());
553                     resetStateMachineForFailure(
554                             ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
555                     return;
556                 }
557                 if (mPasspointConfiguration == null) {
558                     Log.e(TAG, "No PPS MO to use for retrieving TrustCerts");
559                     resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_NO_PPS_MO);
560                     return;
561                 }
562                 retrieveTrustRootCerts(mPasspointConfiguration);
563             } else {
564                 if (mVerboseLoggingEnabled) {
565                     Log.v(TAG, "Received an unexpected SOAP message in state=" + mState);
566                 }
567             }
568         }
569 
570         /**
571          * Installs the trust root CA certificates for AAA, Remediation and Policy Server
572          *
573          * @param sessionId             indicating current session ID
574          * @param trustRootCertificates trust root CA certificates to be installed.
575          */
installTrustRootCertificates(int sessionId, @NonNull Map<Integer, List<X509Certificate>> trustRootCertificates)576         public void installTrustRootCertificates(int sessionId,
577                 @NonNull Map<Integer, List<X509Certificate>> trustRootCertificates) {
578             if (sessionId != mCurrentSessionId) {
579                 Log.w(TAG, "Expected TrustRootCertificates callback for currentSessionId="
580                         + mCurrentSessionId);
581                 return;
582             }
583             if (mState != STATE_WAITING_FOR_TRUST_ROOT_CERTS) {
584                 if (mVerboseLoggingEnabled) {
585                     Log.v(TAG, "Received an unexpected TrustRootCertificates in state=" + mState);
586                 }
587                 return;
588             }
589 
590             if (trustRootCertificates.isEmpty()) {
591                 Log.e(TAG, "fails to retrieve trust root certificates");
592                 resetStateMachineForFailure(
593                         ProvisioningCallback.OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES);
594                 return;
595             }
596 
597             List<X509Certificate> certificates = trustRootCertificates.get(
598                     OsuServerConnection.TRUST_CERT_TYPE_AAA);
599             if (certificates == null || certificates.isEmpty()) {
600                 Log.e(TAG, "fails to retrieve trust root certificate for AAA server");
601                 resetStateMachineForFailure(
602                         ProvisioningCallback.OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE);
603                 return;
604             }
605 
606             // Save the service friendly names from OsuProvider to keep this in the profile.
607             mPasspointConfiguration.setServiceFriendlyNames(mOsuProvider.getFriendlyNameList());
608 
609             mPasspointConfiguration.getCredential().setCaCertificates(
610                     certificates.toArray(new X509Certificate[0]));
611 
612             certificates = trustRootCertificates.get(
613                     OsuServerConnection.TRUST_CERT_TYPE_REMEDIATION);
614             if (certificates == null || certificates.isEmpty()) {
615                 Log.e(TAG, "fails to retrieve trust root certificate for Remediation");
616                 resetStateMachineForFailure(
617                         ProvisioningCallback.OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES);
618                 return;
619             }
620 
621             if (mPasspointConfiguration.getSubscriptionUpdate() != null) {
622                 mPasspointConfiguration.getSubscriptionUpdate().setCaCertificate(
623                         certificates.get(0));
624             }
625 
626             try {
627                 mWifiManager.addOrUpdatePasspointConfiguration(mPasspointConfiguration);
628             } catch (IllegalArgumentException e) {
629                 Log.e(TAG, "fails to add a new PasspointConfiguration: " + e);
630                 resetStateMachineForFailure(
631                         ProvisioningCallback.OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION);
632                 return;
633             }
634 
635             invokeProvisioningCompleteCallback();
636             if (mVerboseLoggingEnabled) {
637                 Log.i(TAG, "Provisioning is complete for "
638                         + mPasspointConfiguration.getHomeSp().getFqdn());
639             }
640             resetStateMachine();
641         }
642 
643         /**
644          * Disconnect event received
645          *
646          * Note: Called on main thread (WifiService thread).
647          */
handleDisconnect()648         public void handleDisconnect() {
649             if (mVerboseLoggingEnabled) {
650                 Log.v(TAG, "Connection failed in state=" + mState);
651             }
652             if (mState == STATE_INIT) {
653                 Log.w(TAG, "Disconnect event unhandled in state=" + mState);
654                 return;
655             }
656             mNetwork = null;
657             resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
658         }
659 
660         /**
661          * Establishes TLS session to the server(OSU Server, Remediation or Policy Server).
662          *
663          * @param network current {@link Network} associated with the target AP.
664          */
initiateServerConnection(Network network)665         private void initiateServerConnection(Network network) {
666             if (mVerboseLoggingEnabled) {
667                 Log.v(TAG, "Initiating server connection in state=" + mState);
668             }
669 
670             if (!mOsuServerConnection.connect(mServerUrl, network)) {
671                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
672                 return;
673             }
674             mNetwork = network;
675             changeState(STATE_OSU_SERVER_CONNECTING);
676             invokeProvisioningCallback(PROVISIONING_STATUS,
677                     ProvisioningCallback.OSU_STATUS_SERVER_CONNECTING);
678         }
679 
invokeProvisioningCallback(int callbackType, int status)680         private void invokeProvisioningCallback(int callbackType, int status) {
681             if (mProvisioningCallback == null) {
682                 Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status
683                         + " not invoked");
684                 return;
685             }
686             try {
687                 if (callbackType == PROVISIONING_STATUS) {
688                     mProvisioningCallback.onProvisioningStatus(status);
689                 } else {
690                     mProvisioningCallback.onProvisioningFailure(status);
691                 }
692             } catch (RemoteException e) {
693                 Log.e(TAG, "Remote Exception while posting callback type=" + callbackType
694                         + " status=" + status);
695             }
696         }
697 
invokeProvisioningCompleteCallback()698         private void invokeProvisioningCompleteCallback() {
699             mWifiMetrics.incrementPasspointProvisionSuccess();
700             if (mProvisioningCallback == null) {
701                 Log.e(TAG, "No provisioning complete callback registered");
702                 return;
703             }
704             try {
705                 mProvisioningCallback.onProvisioningComplete();
706             } catch (RemoteException e) {
707                 Log.e(TAG, "Remote Exception while posting provisioning complete");
708             }
709         }
710 
711         /**
712          * Initiates the SOAP message exchange with sending the sppPostDevData message.
713          */
initSoapExchange()714         private void initSoapExchange() {
715             if (mVerboseLoggingEnabled) {
716                 Log.v(TAG, "Initiates soap message exchange in state =" + mState);
717             }
718 
719             if (mState != STATE_OSU_SERVER_CONNECTING) {
720                 Log.e(TAG, "Initiates soap message exchange in wrong state=" + mState);
721                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
722                 return;
723             }
724 
725             // Redirect uri used for signal of completion for registration process.
726             final URL redirectUri = mRedirectListener.getServerUrl();
727 
728             // Sending the first sppPostDevDataRequest message.
729             if (mOsuServerConnection.exchangeSoapMessage(
730                     PostDevDataMessage.serializeToSoapEnvelope(mContext, mSystemInfo,
731                             redirectUri.toString(),
732                             SppConstants.SppReason.SUBSCRIPTION_REGISTRATION, null))) {
733                 invokeProvisioningCallback(PROVISIONING_STATUS,
734                         ProvisioningCallback.OSU_STATUS_INIT_SOAP_EXCHANGE);
735                 // Move to initiate soap exchange
736                 changeState(STATE_WAITING_FOR_FIRST_SOAP_RESPONSE);
737             } else {
738                 Log.e(TAG, "HttpsConnection is not established for soap message exchange");
739                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
740                 return;
741             }
742         }
743 
744         /**
745          * Launches OsuLogin Application for users to register a new subscription.
746          */
launchOsuWebView()747         private void launchOsuWebView() {
748             if (mVerboseLoggingEnabled) {
749                 Log.v(TAG, "launch Osu webview in state =" + mState);
750             }
751 
752             if (mState != STATE_WAITING_FOR_FIRST_SOAP_RESPONSE) {
753                 Log.e(TAG, "launch Osu webview in wrong state =" + mState);
754                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
755                 return;
756             }
757 
758             // Start the redirect server to listen the HTTP redirect response from server
759             // as completion of user input.
760             if (!mRedirectListener.startServer(new RedirectListener.RedirectCallback() {
761                 /** Called on different thread (RedirectListener thread). */
762                 @Override
763                 public void onRedirectReceived() {
764                     if (mVerboseLoggingEnabled) {
765                         Log.v(TAG, "Received HTTP redirect response");
766                     }
767                     mProvisioningStateMachine.getHandler().post(() -> handleRedirectResponse());
768                 }
769 
770                 /** Called on main thread (WifiService thread). */
771                 @Override
772                 public void onRedirectTimedOut() {
773                     if (mVerboseLoggingEnabled) {
774                         Log.v(TAG, "Timed out to receive a HTTP redirect response");
775                     }
776                     mProvisioningStateMachine.handleTimeOutForRedirectResponse();
777                 }
778             }, mRedirectStartStopHandler)) {
779                 Log.e(TAG, "fails to start redirect listener");
780                 resetStateMachineForFailure(
781                         ProvisioningCallback.OSU_FAILURE_START_REDIRECT_LISTENER);
782                 return;
783             }
784 
785             Intent intent = new Intent(WifiManager.ACTION_PASSPOINT_LAUNCH_OSU_VIEW);
786             intent.putExtra(WifiManager.EXTRA_OSU_NETWORK, mNetwork);
787             intent.putExtra(WifiManager.EXTRA_URL, mWebUrl);
788             intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
789 
790             List<ResolveInfo> resolveInfos = mContext.getPackageManager()
791                     .queryIntentActivities(
792                             intent,
793                             PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_SYSTEM_ONLY);
794             if (resolveInfos == null || resolveInfos.isEmpty()) {
795                 Log.e(TAG, "can't resolve the activity for the intent");
796                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_NO_OSU_ACTIVITY_FOUND);
797                 return;
798             }
799 
800             if (resolveInfos.size() > 1) {
801                 if (mVerboseLoggingEnabled) {
802                     Log.i(TAG, "Multiple OsuLogin apps found: "
803                             + resolveInfos.stream()
804                                     .map(info -> info.activityInfo.applicationInfo.packageName)
805                                     .collect(Collectors.joining(", ")));
806                 }
807 
808                 // if multiple apps are found, filter out the default implementation supplied
809                 // in the Wifi apex and let other implementations override.
810                 resolveInfos.removeIf(info ->
811                         Environment.isAppInWifiApex(info.activityInfo.applicationInfo));
812             }
813             // forcefully resolve to the first one
814             String packageName = resolveInfos.get(0).activityInfo.applicationInfo.packageName;
815             intent.setPackage(packageName);
816             if (mVerboseLoggingEnabled) {
817                 Log.i(TAG, "Opening OsuLogin app: " + packageName);
818             }
819             mContext.startActivityAsUser(intent, UserHandle.CURRENT);
820             invokeProvisioningCallback(PROVISIONING_STATUS,
821                     ProvisioningCallback.OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE);
822             changeState(STATE_WAITING_FOR_REDIRECT_RESPONSE);
823         }
824 
825         /**
826          * Initiates the second SOAP message exchange with sending the sppPostDevData message.
827          */
secondSoapExchange()828         private void secondSoapExchange() {
829             if (mVerboseLoggingEnabled) {
830                 Log.v(TAG, "Initiates the second soap message exchange in state =" + mState);
831             }
832 
833             if (mState != STATE_WAITING_FOR_REDIRECT_RESPONSE) {
834                 Log.e(TAG, "Initiates the second soap message exchange in wrong state=" + mState);
835                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
836                 return;
837             }
838 
839             // Sending the second sppPostDevDataRequest message.
840             if (mOsuServerConnection.exchangeSoapMessage(
841                     PostDevDataMessage.serializeToSoapEnvelope(mContext, mSystemInfo,
842                             mRedirectListener.getServerUrl().toString(),
843                             SppConstants.SppReason.USER_INPUT_COMPLETED, mSessionId))) {
844                 invokeProvisioningCallback(PROVISIONING_STATUS,
845                         ProvisioningCallback.OSU_STATUS_SECOND_SOAP_EXCHANGE);
846                 changeState(STATE_WAITING_FOR_SECOND_SOAP_RESPONSE);
847             } else {
848                 Log.e(TAG, "HttpsConnection is not established for soap message exchange");
849                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
850                 return;
851             }
852         }
853 
854         /**
855          * Initiates the third SOAP message exchange with sending the sppUpdateResponse message.
856          */
thirdSoapExchange(boolean isError)857         private void thirdSoapExchange(boolean isError) {
858             if (mVerboseLoggingEnabled) {
859                 Log.v(TAG, "Initiates the third soap message exchange in state =" + mState);
860             }
861 
862             if (mState != STATE_WAITING_FOR_SECOND_SOAP_RESPONSE) {
863                 Log.e(TAG, "Initiates the third soap message exchange in wrong state=" + mState);
864                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_PROVISIONING_ABORTED);
865                 return;
866             }
867 
868             // Sending the sppUpdateResponse message.
869             if (mOsuServerConnection.exchangeSoapMessage(
870                     UpdateResponseMessage.serializeToSoapEnvelope(mSessionId, isError))) {
871                 invokeProvisioningCallback(PROVISIONING_STATUS,
872                         ProvisioningCallback.OSU_STATUS_THIRD_SOAP_EXCHANGE);
873                 changeState(STATE_WAITING_FOR_THIRD_SOAP_RESPONSE);
874             } else {
875                 Log.e(TAG, "HttpsConnection is not established for soap message exchange");
876                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SOAP_MESSAGE_EXCHANGE);
877                 return;
878             }
879         }
880 
881         /**
882          * Builds {@link PasspointConfiguration} object from PPS(PerProviderSubscription)
883          * MO(Management Object).
884          */
buildPasspointConfiguration(@onNull PpsMoData moData)885         private PasspointConfiguration buildPasspointConfiguration(@NonNull PpsMoData moData) {
886             String moTree = moData.getPpsMoTree();
887 
888             PasspointConfiguration passpointConfiguration = PpsMoParser.parseMoText(moTree);
889             if (passpointConfiguration == null) {
890                 Log.e(TAG, "fails to parse the MoTree");
891                 return null;
892             }
893 
894             if (!passpointConfiguration.validateForR2()) {
895                 Log.e(TAG, "PPS MO received is invalid: " + passpointConfiguration);
896                 return null;
897             }
898 
899             if (mVerboseLoggingEnabled) {
900                 Log.d(TAG, "The parsed PasspointConfiguration: " + passpointConfiguration);
901             }
902 
903             return passpointConfiguration;
904         }
905 
906         /**
907          * Retrieves Trust Root CA Certificates from server url defined in PPS
908          * (PerProviderSubscription) MO(Management Object).
909          */
retrieveTrustRootCerts(@onNull PasspointConfiguration passpointConfig)910         private void retrieveTrustRootCerts(@NonNull PasspointConfiguration passpointConfig) {
911             if (mVerboseLoggingEnabled) {
912                 Log.v(TAG, "Initiates retrieving trust root certs in state =" + mState);
913             }
914 
915             Map<String, byte[]> trustCertInfo = passpointConfig.getTrustRootCertList();
916             if (trustCertInfo == null || trustCertInfo.isEmpty()) {
917                 Log.e(TAG, "no AAATrustRoot Node found");
918                 resetStateMachineForFailure(
919                         ProvisioningCallback.OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE);
920                 return;
921             }
922             Map<Integer, Map<String, byte[]>> allTrustCerts = new HashMap<>();
923             allTrustCerts.put(OsuServerConnection.TRUST_CERT_TYPE_AAA, trustCertInfo);
924 
925             // SubscriptionUpdate is a required node.
926             if (passpointConfig.getSubscriptionUpdate() != null
927                     && passpointConfig.getSubscriptionUpdate().getTrustRootCertUrl() != null) {
928                 trustCertInfo = new HashMap<>();
929                 trustCertInfo.put(
930                         passpointConfig.getSubscriptionUpdate().getTrustRootCertUrl(),
931                         passpointConfig.getSubscriptionUpdate()
932                                 .getTrustRootCertSha256Fingerprint());
933                 allTrustCerts.put(OsuServerConnection.TRUST_CERT_TYPE_REMEDIATION, trustCertInfo);
934             } else {
935                 Log.e(TAG, "no TrustRoot Node for remediation server found");
936                 resetStateMachineForFailure(
937                         ProvisioningCallback.OSU_FAILURE_NO_REMEDIATION_SERVER_TRUST_ROOT_NODE);
938                 return;
939             }
940 
941             // Policy is an optional node
942             if (passpointConfig.getPolicy() != null) {
943                 if (passpointConfig.getPolicy().getPolicyUpdate() != null
944                         && passpointConfig.getPolicy().getPolicyUpdate().getTrustRootCertUrl()
945                         != null) {
946                     trustCertInfo = new HashMap<>();
947                     trustCertInfo.put(
948                             passpointConfig.getPolicy().getPolicyUpdate()
949                                     .getTrustRootCertUrl(),
950                             passpointConfig.getPolicy().getPolicyUpdate()
951                                     .getTrustRootCertSha256Fingerprint());
952                     allTrustCerts.put(OsuServerConnection.TRUST_CERT_TYPE_POLICY, trustCertInfo);
953                 } else {
954                     Log.e(TAG, "no TrustRoot Node for policy server found");
955                     resetStateMachineForFailure(
956                             ProvisioningCallback.OSU_FAILURE_NO_POLICY_SERVER_TRUST_ROOT_NODE);
957                     return;
958                 }
959             }
960 
961             if (mOsuServerConnection.retrieveTrustRootCerts(allTrustCerts)) {
962                 invokeProvisioningCallback(PROVISIONING_STATUS,
963                         ProvisioningCallback.OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS);
964                 changeState(STATE_WAITING_FOR_TRUST_ROOT_CERTS);
965             } else {
966                 Log.e(TAG, "HttpsConnection is not established for retrieving trust root certs");
967                 resetStateMachineForFailure(ProvisioningCallback.OSU_FAILURE_SERVER_CONNECTION);
968                 return;
969             }
970         }
971 
changeState(int nextState)972         private void changeState(int nextState) {
973             if (nextState != mState) {
974                 if (mVerboseLoggingEnabled) {
975                     Log.v(TAG, "Changing state from " + mState + " -> " + nextState);
976                 }
977                 mState = nextState;
978             }
979         }
980 
resetStateMachineForFailure(int failureCode)981         private void resetStateMachineForFailure(int failureCode) {
982             mWifiMetrics.incrementPasspointProvisionFailure(failureCode);
983             invokeProvisioningCallback(PROVISIONING_FAILURE, failureCode);
984             resetStateMachine();
985         }
986 
resetStateMachine()987         private void resetStateMachine() {
988             if (mRedirectListener != null) {
989                 mRedirectListener.stopServer(mRedirectStartStopHandler);
990             }
991             mOsuNetworkConnection.setEventCallback(null);
992             mOsuNetworkConnection.disconnectIfNeeded();
993             mOsuServerConnection.setEventCallback(null);
994             mOsuServerConnection.cleanup();
995             mPasspointConfiguration = null;
996             mProvisioningCallback = null;
997             changeState(STATE_INIT);
998         }
999 
1000         /**
1001          * Get a best matching osuProvider from scanResults with provided osuProvider
1002          *
1003          * @param scanResults a list of {@link ScanResult} to find a best osuProvider
1004          * @param osuProvider an instance of {@link OsuProvider} used to match with scanResults
1005          * @return a best matching {@link OsuProvider}, {@code null} when an invalid scanResults are
1006          * provided or no match is found.
1007          */
getBestMatchingOsuProvider( List<ScanResult> scanResults, OsuProvider osuProvider)1008         private OsuProvider getBestMatchingOsuProvider(
1009                 List<ScanResult> scanResults,
1010                 OsuProvider osuProvider) {
1011             if (scanResults == null) {
1012                 Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult");
1013                 return null;
1014             }
1015 
1016             if (osuProvider == null) {
1017                 Log.e(TAG, "Attempt to retrieve best OSU provider for a null osuProvider");
1018                 return null;
1019             }
1020 
1021             // Clear the OSU SSID to compare it with other OsuProviders only about service
1022             // provider information.
1023             osuProvider.setOsuSsid(null);
1024 
1025             // Filter non-Passpoint AP out and sort it by descending order of signal strength.
1026             scanResults.removeIf((scanResult) -> !scanResult.isPasspointNetwork());
1027             scanResults.sort((sr1, sr2) -> sr2.level - sr1.level);
1028 
1029             for (ScanResult scanResult : scanResults) {
1030                 // Lookup OSU Providers ANQP element by ANQPNetworkKey.
1031                 // It might have same ANQP element with another one which has same ANQP domain id.
1032                 Map<Constants.ANQPElementType, ANQPElement> anqpElements =
1033                         mPasspointManager.getANQPElements(
1034                                 scanResult);
1035                 HSOsuProvidersElement element =
1036                         (HSOsuProvidersElement) anqpElements.get(
1037                                 Constants.ANQPElementType.HSOSUProviders);
1038                 if (element == null) continue;
1039                 for (OsuProviderInfo info : element.getProviders()) {
1040                     OsuProvider candidate = new OsuProvider(
1041                             (WifiSsid) null, info.getFriendlyNames(),
1042                             info.getServiceDescription(), info.getServerUri(),
1043                             info.getNetworkAccessIdentifier(), info.getMethodList());
1044                     if (candidate.equals(osuProvider)) {
1045                         // Found a matching candidate and then set OSU SSID for the OSU provider.
1046                         candidate.setOsuSsid(element.getOsuSsid());
1047                         return candidate;
1048                     }
1049                 }
1050             }
1051             return null;
1052         }
1053     }
1054 
1055     /**
1056      * Callbacks for network and wifi events
1057      *
1058      * Note: Called on main thread (WifiService thread).
1059      */
1060     class OsuNetworkCallbacks implements OsuNetworkConnection.Callbacks {
1061 
1062         @Override
onConnected(Network network)1063         public void onConnected(Network network) {
1064             if (mVerboseLoggingEnabled) {
1065                 Log.v(TAG, "onConnected to " + network);
1066             }
1067             if (network == null) {
1068                 mProvisioningStateMachine.handleDisconnect();
1069             } else {
1070                 mProvisioningStateMachine.handleConnectedEvent(network);
1071             }
1072         }
1073 
1074         @Override
onDisconnected()1075         public void onDisconnected() {
1076             if (mVerboseLoggingEnabled) {
1077                 Log.v(TAG, "onDisconnected");
1078             }
1079             mProvisioningStateMachine.handleDisconnect();
1080         }
1081 
1082         @Override
onTimeOut()1083         public void onTimeOut() {
1084             if (mVerboseLoggingEnabled) {
1085                 Log.v(TAG, "Timed out waiting for connection to OSU AP");
1086             }
1087             mProvisioningStateMachine.handleDisconnect();
1088         }
1089 
1090         @Override
onWifiEnabled()1091         public void onWifiEnabled() {
1092             if (mVerboseLoggingEnabled) {
1093                 Log.v(TAG, "onWifiEnabled");
1094             }
1095         }
1096 
1097         @Override
onWifiDisabled()1098         public void onWifiDisabled() {
1099             if (mVerboseLoggingEnabled) {
1100                 Log.v(TAG, "onWifiDisabled");
1101             }
1102             mProvisioningStateMachine.handleWifiDisabled();
1103         }
1104     }
1105 
1106     /**
1107      * Defines the callbacks expected from OsuServerConnection
1108      *
1109      * Note: Called on main thread (WifiService thread).
1110      */
1111     public class OsuServerCallbacks {
1112         private final int mSessionId;
1113 
OsuServerCallbacks(int sessionId)1114         OsuServerCallbacks(int sessionId) {
1115             mSessionId = sessionId;
1116         }
1117 
1118         /**
1119          * Returns the session ID corresponding to this callback
1120          *
1121          * @return int sessionID
1122          */
getSessionId()1123         public int getSessionId() {
1124             return mSessionId;
1125         }
1126 
1127         /**
1128          * Callback when a TLS connection to the server is failed.
1129          *
1130          * @param sessionId indicating current session ID
1131          * @param succeeded boolean indicating success/failure of server connection
1132          */
onServerConnectionStatus(int sessionId, boolean succeeded)1133         public void onServerConnectionStatus(int sessionId, boolean succeeded) {
1134             if (mVerboseLoggingEnabled) {
1135                 Log.v(TAG, "OSU Server connection status=" + succeeded + " sessionId=" + sessionId);
1136             }
1137             mProvisioningStateMachine.getHandler().post(() ->
1138                     mProvisioningStateMachine.handleServerConnectionStatus(sessionId, succeeded));
1139         }
1140 
1141         /**
1142          * Provides a server validation status for the session ID
1143          *
1144          * @param sessionId integer indicating current session ID
1145          * @param succeeded boolean indicating success/failure of server validation
1146          */
onServerValidationStatus(int sessionId, boolean succeeded)1147         public void onServerValidationStatus(int sessionId, boolean succeeded) {
1148             if (mVerboseLoggingEnabled) {
1149                 Log.v(TAG, "OSU Server Validation status=" + succeeded + " sessionId=" + sessionId);
1150             }
1151             if (succeeded) {
1152                 mProvisioningStateMachine.getHandler().post(() -> {
1153                     mProvisioningStateMachine.handleServerValidationSuccess(sessionId);
1154                 });
1155             } else {
1156                 mProvisioningStateMachine.getHandler().post(() -> {
1157                     mProvisioningStateMachine.handleServerValidationFailure(sessionId);
1158                 });
1159             }
1160         }
1161 
1162         /**
1163          * Callback when soap message is received from server.
1164          *
1165          * @param sessionId       indicating current session ID
1166          * @param responseMessage SOAP SPP response parsed or {@code null} in any failure
1167          * Note: Called on different thread (OsuServer Thread)!
1168          */
onReceivedSoapMessage(int sessionId, @Nullable SppResponseMessage responseMessage)1169         public void onReceivedSoapMessage(int sessionId,
1170                 @Nullable SppResponseMessage responseMessage) {
1171             if (mVerboseLoggingEnabled) {
1172                 Log.v(TAG, "onReceivedSoapMessage with sessionId=" + sessionId);
1173             }
1174             mProvisioningStateMachine.getHandler().post(() ->
1175                     mProvisioningStateMachine.handleSoapMessageResponse(sessionId,
1176                             responseMessage));
1177         }
1178 
1179         /**
1180          * Callback when trust root certificates are retrieved from server.
1181          *
1182          * @param sessionId             indicating current session ID
1183          * @param trustRootCertificates trust root CA certificates retrieved from server
1184          * Note: Called on different thread (OsuServer Thread)!
1185          */
onReceivedTrustRootCertificates(int sessionId, @NonNull Map<Integer, List<X509Certificate>> trustRootCertificates)1186         public void onReceivedTrustRootCertificates(int sessionId,
1187                 @NonNull Map<Integer, List<X509Certificate>> trustRootCertificates) {
1188             if (mVerboseLoggingEnabled) {
1189                 Log.v(TAG, "onReceivedTrustRootCertificates with sessionId=" + sessionId);
1190             }
1191             mProvisioningStateMachine.getHandler().post(() ->
1192                     mProvisioningStateMachine.installTrustRootCertificates(sessionId,
1193                             trustRootCertificates));
1194         }
1195     }
1196 }
1197