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 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 */ 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 */ 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 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 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 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 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 205 public boolean createdNetwork() { 206 synchronized (mNetworkLock) { 207 return mNetworkCreated; 208 } 209 } 210 211 public FlowType getFlowType() { 212 return mFlowType; 213 } 214 215 public PlatformAdapter getPlatformAdapter() { 216 return mPlatformAdapter; 217 } 218 219 private void setLaunchTime() { 220 mLaunchTime = SystemClock.currentThreadTimeMillis(); 221 } 222 223 public long getLaunchTime() { 224 return mLaunchTime; 225 } 226 227 private int getWifiNetworkId() { 228 synchronized (mNetworkLock) { 229 return mWifiNetworkId; 230 } 231 } 232 233 @Override 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 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 279 public String toString() { 280 return mFlowType + " for " + mSpName; 281 } 282 } 283 284 private class FlowWorker extends Thread { 285 private final PlatformAdapter mPlatformAdapter; 286 287 private FlowWorker(PlatformAdapter platformAdapter) { 288 mPlatformAdapter = platformAdapter; 289 } 290 291 @Override 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 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 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 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