blob: 0123d69881df7e39a27528164080a6884379ee62 [file] [log] [blame]
package com.android.hotspot2.osu;
import android.content.Context;
import android.content.Intent;
import android.net.Network;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.os.SystemClock;
import android.util.Log;
import com.android.hotspot2.Utils;
import com.android.hotspot2.flow.FlowService;
import com.android.hotspot2.flow.OSUInfo;
import com.android.hotspot2.flow.PlatformAdapter;
import com.android.hotspot2.pps.HomeSP;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Iterator;
import java.util.LinkedList;
import javax.net.ssl.KeyManager;
public class OSUFlowManager {
private static final boolean MATCH_BSSID = false;
private static final long WAIT_QUANTA = 10000L;
private static final long WAIT_TIMEOUT = 1800000L;
private final Context mContext;
private final LinkedList<OSUFlow> mQueue;
private FlowWorker mWorker;
private OSUFlow mCurrent;
public OSUFlowManager(Context context) {
mContext = context;
mQueue = new LinkedList<>();
}
public enum FlowType {Provisioning, Remediation, Policy}
public static class OSUFlow implements Runnable {
private final OSUClient mOSUClient;
private final PlatformAdapter mPlatformAdapter;
private final HomeSP mHomeSP;
private final String mSpName;
private final FlowType mFlowType;
private final KeyManager mKeyManager;
private final Object mNetworkLock = new Object();
private final Network mNetwork;
private Network mResultNetwork;
private boolean mNetworkCreated;
private int mWifiNetworkId;
private volatile long mLaunchTime;
private volatile boolean mAborted;
/**
* A policy flow.
* @param osuInfo The OSU information for the flow (SSID, BSSID, URL)
* @param platformAdapter the platform adapter
* @param km A key manager for TLS
* @throws MalformedURLException
*/
public OSUFlow(OSUInfo osuInfo, PlatformAdapter platformAdapter, KeyManager km)
throws MalformedURLException {
mWifiNetworkId = -1;
mNetwork = null;
mOSUClient = new OSUClient(osuInfo,
platformAdapter.getKeyStore(), platformAdapter.getContext());
mPlatformAdapter = platformAdapter;
mHomeSP = null;
mSpName = osuInfo.getName(OSUManager.LOCALE);
mFlowType = FlowType.Provisioning;
mKeyManager = km;
}
/**
* A Remediation flow for credential or policy provisioning.
* @param network The network to use, only set for timed provisioning
* @param osuURL The URL to connect to.
* @param platformAdapter the platform adapter
* @param km A key manager for TLS
* @param homeSP The Home SP to which this remediation flow pertains.
* @param flowType Remediation or Policy
* @throws MalformedURLException
*/
public OSUFlow(Network network, String osuURL,
PlatformAdapter platformAdapter, KeyManager km, HomeSP homeSP,
FlowType flowType) throws MalformedURLException {
mNetwork = network;
mWifiNetworkId = network.netId;
mOSUClient = new OSUClient(osuURL,
platformAdapter.getKeyStore(), platformAdapter.getContext());
mPlatformAdapter = platformAdapter;
mHomeSP = homeSP;
mSpName = homeSP.getFriendlyName();
mFlowType = flowType;
mKeyManager = km;
}
private boolean deleteNetwork(OSUFlow next) {
synchronized (mNetworkLock) {
if (!mNetworkCreated) {
return false;
} else if (next.getFlowType() != FlowType.Provisioning) {
return true;
}
OSUInfo thisInfo = mOSUClient.getOSUInfo();
OSUInfo thatInfo = next.mOSUClient.getOSUInfo();
if (thisInfo.getOsuSsid().equals(thatInfo.getOsuSsid())
&& thisInfo.getOSUBssid() == thatInfo.getOSUBssid()) {
// Reuse the OSU network from previous and carry forward the creation fact.
mNetworkCreated = true;
return false;
} else {
return true;
}
}
}
private Network connect() throws IOException {
Network network = networkMatch();
synchronized (mNetworkLock) {
mResultNetwork = network;
if (mResultNetwork != null) {
return mResultNetwork;
}
}
Log.d(OSUManager.TAG, "No network match for " + toString());
int osuNetworkId = -1;
boolean created = false;
if (mFlowType == FlowType.Provisioning) {
osuNetworkId = mPlatformAdapter.connect(mOSUClient.getOSUInfo());
created = true;
}
synchronized (mNetworkLock) {
mNetworkCreated = created;
if (created) {
mWifiNetworkId = osuNetworkId;
}
Log.d(OSUManager.TAG, String.format("%s waiting for %snet ID %d",
toString(), created ? "created " : "existing ", osuNetworkId));
while (mResultNetwork == null && !mAborted) {
try {
mNetworkLock.wait();
} catch (InterruptedException ie) {
throw new IOException("Interrupted");
}
}
if (mAborted) {
throw new IOException("Aborted");
}
Utils.delay(500L);
}
return mResultNetwork;
}
private Network networkMatch() {
if (mFlowType == FlowType.Provisioning) {
OSUInfo match = mOSUClient.getOSUInfo();
WifiConfiguration config = mPlatformAdapter.getActiveWifiConfig();
if (config != null && bssidMatch(match, mPlatformAdapter)
&& Utils.decodeSsid(config.SSID).equals(match.getOsuSsid())) {
synchronized (mNetworkLock) {
mWifiNetworkId = config.networkId;
}
return mPlatformAdapter.getCurrentNetwork();
}
} else {
WifiConfiguration config = mPlatformAdapter.getActiveWifiConfig();
synchronized (mNetworkLock) {
mWifiNetworkId = config != null ? config.networkId : -1;
}
return mNetwork;
}
return null;
}
private void networkChange() {
WifiInfo connectionInfo = mPlatformAdapter.getConnectionInfo();
if (connectionInfo == null) {
return;
}
Network network = mPlatformAdapter.getCurrentNetwork();
Log.d(OSUManager.TAG, "New network " + network
+ ", current OSU " + mOSUClient.getOSUInfo() +
", addr " + Utils.toIpString(connectionInfo.getIpAddress()));
synchronized (mNetworkLock) {
if (mResultNetwork == null && network != null && connectionInfo.getIpAddress() != 0
&& connectionInfo.getNetworkId() == mWifiNetworkId) {
mResultNetwork = network;
mNetworkLock.notifyAll();
}
}
}
public boolean createdNetwork() {
synchronized (mNetworkLock) {
return mNetworkCreated;
}
}
public FlowType getFlowType() {
return mFlowType;
}
public PlatformAdapter getPlatformAdapter() {
return mPlatformAdapter;
}
private void setLaunchTime() {
mLaunchTime = SystemClock.currentThreadTimeMillis();
}
public long getLaunchTime() {
return mLaunchTime;
}
private int getWifiNetworkId() {
synchronized (mNetworkLock) {
return mWifiNetworkId;
}
}
@Override
public void run() {
try {
Network network = connect();
Log.d(OSUManager.TAG, "OSU SSID Associated at " + network);
if (mFlowType == FlowType.Provisioning) {
mOSUClient.provision(mPlatformAdapter, network, mKeyManager);
} else {
mOSUClient.remediate(mPlatformAdapter, network,
mKeyManager, mHomeSP, mFlowType);
}
} catch (Throwable t) {
if (mAborted) {
Log.d(OSUManager.TAG, "OSU flow aborted: " + t, t);
} else {
Log.w(OSUManager.TAG, "OSU flow failed: " + t, t);
mPlatformAdapter.provisioningFailed(mSpName, t.getMessage());
}
} finally {
if (!mAborted) {
mOSUClient.close(false);
}
}
}
public void abort() {
synchronized (mNetworkLock) {
mAborted = true;
mNetworkLock.notifyAll();
}
// Sockets cannot be closed on the main thread...
// TODO: Might want to change this to a handler.
new Thread() {
@Override
public void run() {
try {
mOSUClient.close(true);
} catch (Throwable t) {
Log.d(OSUManager.TAG, "Exception aborting " + toString());
}
}
}.start();
}
@Override
public String toString() {
return mFlowType + " for " + mSpName;
}
}
private class FlowWorker extends Thread {
private final PlatformAdapter mPlatformAdapter;
private FlowWorker(PlatformAdapter platformAdapter) {
mPlatformAdapter = platformAdapter;
}
@Override
public void run() {
for (; ; ) {
synchronized (mQueue) {
if (mCurrent != null && mCurrent.createdNetwork()
&& (mQueue.isEmpty() || mCurrent.deleteNetwork(mQueue.getLast()))) {
mPlatformAdapter.deleteNetwork(mCurrent.getWifiNetworkId());
}
mCurrent = null;
while (mQueue.isEmpty()) {
try {
mQueue.wait(WAIT_QUANTA);
} catch (InterruptedException ie) {
return;
}
if (mQueue.isEmpty()) {
// Bail out on time out
Log.d(OSUManager.TAG, "Flow worker terminating.");
mWorker = null;
mContext.stopService(new Intent(mContext, FlowService.class));
return;
}
}
mCurrent = mQueue.removeLast();
mCurrent.setLaunchTime();
}
Log.d(OSUManager.TAG, "Starting " + mCurrent);
mCurrent.run();
Log.d(OSUManager.TAG, "Exiting " + mCurrent);
}
}
}
/*
* Provisioning: Wait until there is an active WiFi info and the active WiFi config
* matches SSID and optionally BSSID.
* WNM Remediation: Wait until the active WiFi info matches BSSID.
* Timed remediation: The network is given (may be cellular).
*/
public void appendFlow(OSUFlow flow) {
synchronized (mQueue) {
if (mCurrent != null &&
SystemClock.currentThreadTimeMillis()
- mCurrent.getLaunchTime() >= WAIT_TIMEOUT) {
Log.d(OSUManager.TAG, "Aborting stale OSU flow " + mCurrent);
mCurrent.abort();
mCurrent = null;
}
if (flow.getFlowType() == FlowType.Provisioning) {
// Kill any outstanding provisioning flows.
Iterator<OSUFlow> flows = mQueue.iterator();
while (flows.hasNext()) {
if (flows.next().getFlowType() == FlowType.Provisioning) {
flows.remove();
}
}
if (mCurrent != null
&& mCurrent.getFlowType() == FlowType.Provisioning) {
Log.d(OSUManager.TAG, "Aborting current provisioning flow " + mCurrent);
mCurrent.abort();
mCurrent = null;
}
mQueue.addLast(flow);
} else {
mQueue.addFirst(flow);
}
if (mWorker == null) {
// TODO: Might want to change this to a handler.
mWorker = new FlowWorker(flow.getPlatformAdapter());
mWorker.start();
}
mQueue.notifyAll();
}
}
public void networkChange() {
OSUFlow pending;
synchronized (mQueue) {
pending = mCurrent;
}
Log.d(OSUManager.TAG, "Network change, current flow: " + pending);
if (pending != null) {
pending.networkChange();
}
}
private static boolean bssidMatch(OSUInfo osuInfo, PlatformAdapter platformAdapter) {
if (MATCH_BSSID) {
WifiInfo wifiInfo = platformAdapter.getConnectionInfo();
return wifiInfo != null && Utils.parseMac(wifiInfo.getBSSID()) == osuInfo.getOSUBssid();
} else {
return true;
}
}
}