• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.hotspot2.osu;
2 
3 import android.content.Context;
4 import android.content.Intent;
5 import android.net.Network;
6 import android.net.wifi.WifiConfiguration;
7 import android.net.wifi.WifiInfo;
8 import android.os.SystemClock;
9 import android.util.Log;
10 
11 import com.android.hotspot2.Utils;
12 import com.android.hotspot2.flow.FlowService;
13 import com.android.hotspot2.flow.OSUInfo;
14 import com.android.hotspot2.flow.PlatformAdapter;
15 import com.android.hotspot2.pps.HomeSP;
16 
17 import java.io.IOException;
18 import java.net.MalformedURLException;
19 import java.util.Iterator;
20 import java.util.LinkedList;
21 
22 import javax.net.ssl.KeyManager;
23 
24 public class OSUFlowManager {
25     private static final boolean MATCH_BSSID = false;
26     private static final long WAIT_QUANTA = 10000L;
27     private static final long WAIT_TIMEOUT = 1800000L;
28 
29     private final Context mContext;
30     private final LinkedList<OSUFlow> mQueue;
31     private FlowWorker mWorker;
32     private OSUFlow mCurrent;
33 
OSUFlowManager(Context context)34     public OSUFlowManager(Context context) {
35         mContext = context;
36         mQueue = new LinkedList<>();
37     }
38 
39     public enum FlowType {Provisioning, Remediation, Policy}
40 
41     public static class OSUFlow implements Runnable {
42         private final OSUClient mOSUClient;
43         private final PlatformAdapter mPlatformAdapter;
44         private final HomeSP mHomeSP;
45         private final String mSpName;
46         private final FlowType mFlowType;
47         private final KeyManager mKeyManager;
48         private final Object mNetworkLock = new Object();
49         private final Network mNetwork;
50         private Network mResultNetwork;
51         private boolean mNetworkCreated;
52         private int mWifiNetworkId;
53         private volatile long mLaunchTime;
54         private volatile boolean mAborted;
55 
56         /**
57          * A policy flow.
58          * @param osuInfo The OSU information for the flow (SSID, BSSID, URL)
59          * @param platformAdapter the platform adapter
60          * @param km A key manager for TLS
61          * @throws MalformedURLException
62          */
OSUFlow(OSUInfo osuInfo, PlatformAdapter platformAdapter, KeyManager km)63         public OSUFlow(OSUInfo osuInfo, PlatformAdapter platformAdapter, KeyManager km)
64                 throws MalformedURLException {
65 
66             mWifiNetworkId = -1;
67             mNetwork = null;
68             mOSUClient = new OSUClient(osuInfo,
69                     platformAdapter.getKeyStore(), platformAdapter.getContext());
70             mPlatformAdapter = platformAdapter;
71             mHomeSP = null;
72             mSpName = osuInfo.getName(OSUManager.LOCALE);
73             mFlowType = FlowType.Provisioning;
74             mKeyManager = km;
75         }
76 
77         /**
78          * A Remediation flow for credential or policy provisioning.
79          * @param network The network to use, only set for timed provisioning
80          * @param osuURL The URL to connect to.
81          * @param platformAdapter the platform adapter
82          * @param km A key manager for TLS
83          * @param homeSP The Home SP to which this remediation flow pertains.
84          * @param flowType Remediation or Policy
85          * @throws MalformedURLException
86          */
OSUFlow(Network network, String osuURL, PlatformAdapter platformAdapter, KeyManager km, HomeSP homeSP, FlowType flowType)87         public OSUFlow(Network network, String osuURL,
88                        PlatformAdapter platformAdapter, KeyManager km, HomeSP homeSP,
89                        FlowType flowType) throws MalformedURLException {
90 
91             mNetwork = network;
92             mWifiNetworkId = network.netId;
93             mOSUClient = new OSUClient(osuURL,
94                     platformAdapter.getKeyStore(), platformAdapter.getContext());
95             mPlatformAdapter = platformAdapter;
96             mHomeSP = homeSP;
97             mSpName = homeSP.getFriendlyName();
98             mFlowType = flowType;
99             mKeyManager = km;
100         }
101 
deleteNetwork(OSUFlow next)102         private boolean deleteNetwork(OSUFlow next) {
103             synchronized (mNetworkLock) {
104                 if (!mNetworkCreated) {
105                     return false;
106                 } else if (next.getFlowType() != FlowType.Provisioning) {
107                     return true;
108                 }
109                 OSUInfo thisInfo = mOSUClient.getOSUInfo();
110                 OSUInfo thatInfo = next.mOSUClient.getOSUInfo();
111                 if (thisInfo.getOsuSsid().equals(thatInfo.getOsuSsid())
112                         && thisInfo.getOSUBssid() == thatInfo.getOSUBssid()) {
113                     // Reuse the OSU network from previous and carry forward the creation fact.
114                     mNetworkCreated = true;
115                     return false;
116                 } else {
117                     return true;
118                 }
119             }
120         }
121 
connect()122         private Network connect() throws IOException {
123             Network network = networkMatch();
124 
125             synchronized (mNetworkLock) {
126                 mResultNetwork = network;
127                 if (mResultNetwork != null) {
128                     return mResultNetwork;
129                 }
130             }
131 
132             Log.d(OSUManager.TAG, "No network match for " + toString());
133 
134             int osuNetworkId = -1;
135             boolean created = false;
136 
137             if (mFlowType == FlowType.Provisioning) {
138                 osuNetworkId = mPlatformAdapter.connect(mOSUClient.getOSUInfo());
139                 created = true;
140             }
141 
142             synchronized (mNetworkLock) {
143                 mNetworkCreated = created;
144                 if (created) {
145                     mWifiNetworkId = osuNetworkId;
146                 }
147                 Log.d(OSUManager.TAG, String.format("%s waiting for %snet ID %d",
148                         toString(), created ? "created " : "existing ", osuNetworkId));
149 
150                 while (mResultNetwork == null && !mAborted) {
151                     try {
152                         mNetworkLock.wait();
153                     } catch (InterruptedException ie) {
154                         throw new IOException("Interrupted");
155                     }
156                 }
157                 if (mAborted) {
158                     throw new IOException("Aborted");
159                 }
160                 Utils.delay(500L);
161             }
162             return mResultNetwork;
163         }
164 
networkMatch()165         private Network networkMatch() {
166             if (mFlowType == FlowType.Provisioning) {
167                 OSUInfo match = mOSUClient.getOSUInfo();
168                 WifiConfiguration config = mPlatformAdapter.getActiveWifiConfig();
169                 if (config != null && bssidMatch(match, mPlatformAdapter)
170                         && Utils.decodeSsid(config.SSID).equals(match.getOsuSsid())) {
171                     synchronized (mNetworkLock) {
172                         mWifiNetworkId = config.networkId;
173                     }
174                     return mPlatformAdapter.getCurrentNetwork();
175                 }
176             } else {
177                 WifiConfiguration config = mPlatformAdapter.getActiveWifiConfig();
178                 synchronized (mNetworkLock) {
179                     mWifiNetworkId = config != null ? config.networkId : -1;
180                 }
181                 return mNetwork;
182             }
183             return null;
184         }
185 
networkChange()186         private void networkChange() {
187             WifiInfo connectionInfo = mPlatformAdapter.getConnectionInfo();
188             if (connectionInfo == null) {
189                 return;
190             }
191             Network network = mPlatformAdapter.getCurrentNetwork();
192             Log.d(OSUManager.TAG, "New network " + network
193                     + ", current OSU " + mOSUClient.getOSUInfo() +
194                     ", addr " + Utils.toIpString(connectionInfo.getIpAddress()));
195 
196             synchronized (mNetworkLock) {
197                 if (mResultNetwork == null && network != null && connectionInfo.getIpAddress() != 0
198                         && connectionInfo.getNetworkId() == mWifiNetworkId) {
199                     mResultNetwork = network;
200                     mNetworkLock.notifyAll();
201                 }
202             }
203         }
204 
createdNetwork()205         public boolean createdNetwork() {
206             synchronized (mNetworkLock) {
207                 return mNetworkCreated;
208             }
209         }
210 
getFlowType()211         public FlowType getFlowType() {
212             return mFlowType;
213         }
214 
getPlatformAdapter()215         public PlatformAdapter getPlatformAdapter() {
216             return mPlatformAdapter;
217         }
218 
setLaunchTime()219         private void setLaunchTime() {
220             mLaunchTime = SystemClock.currentThreadTimeMillis();
221         }
222 
getLaunchTime()223         public long getLaunchTime() {
224             return mLaunchTime;
225         }
226 
getWifiNetworkId()227         private int getWifiNetworkId() {
228             synchronized (mNetworkLock) {
229                 return mWifiNetworkId;
230             }
231         }
232 
233         @Override
run()234         public void run() {
235             try {
236                 Network network = connect();
237                 Log.d(OSUManager.TAG, "OSU SSID Associated at " + network);
238 
239                 if (mFlowType == FlowType.Provisioning) {
240                     mOSUClient.provision(mPlatformAdapter, network, mKeyManager);
241                 } else {
242                     mOSUClient.remediate(mPlatformAdapter, network,
243                             mKeyManager, mHomeSP, mFlowType);
244                 }
245             } catch (Throwable t) {
246                 if (mAborted) {
247                     Log.d(OSUManager.TAG, "OSU flow aborted: " + t, t);
248                 } else {
249                     Log.w(OSUManager.TAG, "OSU flow failed: " + t, t);
250                     mPlatformAdapter.provisioningFailed(mSpName, t.getMessage());
251                 }
252             } finally {
253                 if (!mAborted) {
254                     mOSUClient.close(false);
255                 }
256             }
257         }
258 
abort()259         public void abort() {
260             synchronized (mNetworkLock) {
261                 mAborted = true;
262                 mNetworkLock.notifyAll();
263             }
264             // Sockets cannot be closed on the main thread...
265             // TODO: Might want to change this to a handler.
266             new Thread() {
267                 @Override
268                 public void run() {
269                     try {
270                         mOSUClient.close(true);
271                     } catch (Throwable t) {
272                         Log.d(OSUManager.TAG, "Exception aborting " + toString());
273                     }
274                 }
275             }.start();
276         }
277 
278         @Override
toString()279         public String toString() {
280             return mFlowType + " for " + mSpName;
281         }
282     }
283 
284     private class FlowWorker extends Thread {
285         private final PlatformAdapter mPlatformAdapter;
286 
FlowWorker(PlatformAdapter platformAdapter)287         private FlowWorker(PlatformAdapter platformAdapter) {
288             mPlatformAdapter = platformAdapter;
289         }
290 
291         @Override
run()292         public void run() {
293             for (; ; ) {
294                 synchronized (mQueue) {
295                     if (mCurrent != null && mCurrent.createdNetwork()
296                             && (mQueue.isEmpty() || mCurrent.deleteNetwork(mQueue.getLast()))) {
297                         mPlatformAdapter.deleteNetwork(mCurrent.getWifiNetworkId());
298                     }
299 
300                     mCurrent = null;
301                     while (mQueue.isEmpty()) {
302                         try {
303                             mQueue.wait(WAIT_QUANTA);
304                         } catch (InterruptedException ie) {
305                             return;
306                         }
307                         if (mQueue.isEmpty()) {
308                             // Bail out on time out
309                             Log.d(OSUManager.TAG, "Flow worker terminating.");
310                             mWorker = null;
311                             mContext.stopService(new Intent(mContext, FlowService.class));
312                             return;
313                         }
314                     }
315                     mCurrent = mQueue.removeLast();
316                     mCurrent.setLaunchTime();
317                 }
318                 Log.d(OSUManager.TAG, "Starting " + mCurrent);
319                 mCurrent.run();
320                 Log.d(OSUManager.TAG, "Exiting " + mCurrent);
321             }
322         }
323     }
324 
325     /*
326      * Provisioning:    Wait until there is an active WiFi info and the active WiFi config
327      *                  matches SSID and optionally BSSID.
328      * WNM Remediation: Wait until the active WiFi info matches BSSID.
329      * Timed remediation: The network is given (may be cellular).
330      */
331 
appendFlow(OSUFlow flow)332     public void appendFlow(OSUFlow flow) {
333         synchronized (mQueue) {
334             if (mCurrent != null &&
335                     SystemClock.currentThreadTimeMillis()
336                             - mCurrent.getLaunchTime() >= WAIT_TIMEOUT) {
337                 Log.d(OSUManager.TAG, "Aborting stale OSU flow " + mCurrent);
338                 mCurrent.abort();
339                 mCurrent = null;
340             }
341 
342             if (flow.getFlowType() == FlowType.Provisioning) {
343                 // Kill any outstanding provisioning flows.
344                 Iterator<OSUFlow> flows = mQueue.iterator();
345                 while (flows.hasNext()) {
346                     if (flows.next().getFlowType() == FlowType.Provisioning) {
347                         flows.remove();
348                     }
349                 }
350 
351                 if (mCurrent != null
352                         && mCurrent.getFlowType() == FlowType.Provisioning) {
353                     Log.d(OSUManager.TAG, "Aborting current provisioning flow " + mCurrent);
354                     mCurrent.abort();
355                     mCurrent = null;
356                 }
357 
358                 mQueue.addLast(flow);
359             } else {
360                 mQueue.addFirst(flow);
361             }
362 
363             if (mWorker == null) {
364                 // TODO: Might want to change this to a handler.
365                 mWorker = new FlowWorker(flow.getPlatformAdapter());
366                 mWorker.start();
367             }
368 
369             mQueue.notifyAll();
370         }
371     }
372 
networkChange()373     public void networkChange() {
374         OSUFlow pending;
375         synchronized (mQueue) {
376             pending = mCurrent;
377         }
378         Log.d(OSUManager.TAG, "Network change, current flow: " + pending);
379         if (pending != null) {
380             pending.networkChange();
381         }
382     }
383 
bssidMatch(OSUInfo osuInfo, PlatformAdapter platformAdapter)384     private static boolean bssidMatch(OSUInfo osuInfo, PlatformAdapter platformAdapter) {
385         if (MATCH_BSSID) {
386             WifiInfo wifiInfo = platformAdapter.getConnectionInfo();
387             return wifiInfo != null && Utils.parseMac(wifiInfo.getBSSID()) == osuInfo.getOSUBssid();
388         } else {
389             return true;
390         }
391     }
392 }
393