Home | History | Annotate | Download | only in osu
      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