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