content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.wifi;
  
  import android.net.wifi.SupplicantState;
  import android.net.wifi.WifiEnterpriseConfig;
  import android.net.wifi.WifiManager;
  import android.net.wifi.WifiSsid;
  import android.os.Handler;
  import android.os.Message;
  import android.util.ArraySet;
  import android.util.Log;
  import android.util.SparseArray;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.util.Protocol;
  import com.android.server.wifi.hotspot2.AnqpEvent;
  import com.android.server.wifi.hotspot2.IconEvent;
  import com.android.server.wifi.hotspot2.WnmData;
  import com.android.server.wifi.util.TelephonyUtil.SimAuthRequestData;
  
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * Listen for events from the wpa_supplicant & wificond and broadcast them on
   * to the various {@link ClientModeImpl} modules interested in handling these events.
   * @hide
   */
  public class WifiMonitor {
      private static final String TAG = "WifiMonitor";
  
      /* Supplicant events reported to a state machine */
      private static final int BASE = Protocol.BASE_WIFI_MONITOR;
  
      /* Connection to supplicant established */
      public static final int SUP_CONNECTION_EVENT                 = BASE + 1;
      /* Connection to supplicant lost */
      public static final int SUP_DISCONNECTION_EVENT              = BASE + 2;
     /* Network connection completed */
      public static final int NETWORK_CONNECTION_EVENT             = BASE + 3;
      /* Network disconnection completed */
      public static final int NETWORK_DISCONNECTION_EVENT          = BASE + 4;
      /* Scan results are available */
      public static final int SCAN_RESULTS_EVENT                   = BASE + 5;
      /* Supplicate state changed */
      public static final int SUPPLICANT_STATE_CHANGE_EVENT        = BASE + 6;
      /* Password failure and EAP authentication failure */
      public static final int AUTHENTICATION_FAILURE_EVENT         = BASE + 7;
      /* WPS success detected */
      public static final int WPS_SUCCESS_EVENT                    = BASE + 8;
      /* WPS failure detected */
      public static final int WPS_FAIL_EVENT                       = BASE + 9;
       /* WPS overlap detected */
      public static final int WPS_OVERLAP_EVENT                    = BASE + 10;
       /* WPS timeout detected */
      public static final int WPS_TIMEOUT_EVENT                    = BASE + 11;
  
      /* Request Identity */
      public static final int SUP_REQUEST_IDENTITY                 = BASE + 15;
  
      /* Request SIM Auth */
      public static final int SUP_REQUEST_SIM_AUTH                 = BASE + 16;
  
      public static final int SCAN_FAILED_EVENT                    = BASE + 17;
      /* Pno scan results are available */
      public static final int PNO_SCAN_RESULTS_EVENT               = BASE + 18;
  
  
      /* Indicates assoc reject event */
      public static final int ASSOCIATION_REJECTION_EVENT          = BASE + 43;
      public static final int ANQP_DONE_EVENT                      = BASE + 44;
  
      /* hotspot 2.0 ANQP events */
      public static final int GAS_QUERY_START_EVENT                = BASE + 51;
      public static final int GAS_QUERY_DONE_EVENT                 = BASE + 52;
      public static final int RX_HS20_ANQP_ICON_EVENT              = BASE + 53;
  
      /* hotspot 2.0 events */
      public static final int HS20_REMEDIATION_EVENT               = BASE + 61;
  
      /* WPS config errrors */
      private static final int CONFIG_MULTIPLE_PBC_DETECTED = 12;
      private static final int CONFIG_AUTH_FAILURE = 18;
  
      /* WPS error indications */
      private static final int REASON_TKIP_ONLY_PROHIBITED = 1;
      private static final int REASON_WEP_PROHIBITED = 2;
  
      private final WifiInjector mWifiInjector;
      private boolean mVerboseLoggingEnabled = false;
      private boolean mConnected = false;
  
      public WifiMonitor(WifiInjector wifiInjector) {
          mWifiInjector = wifiInjector;
      }
  
      void enableVerboseLogging(int verbose) {
          if (verbose > 0) {
              mVerboseLoggingEnabled = true;
          } else {
              mVerboseLoggingEnabled = false;
          }
      }
  
      private final Map<String, SparseArray<Set<Handler>>> mHandlerMap = new HashMap<>();
      public synchronized void registerHandler(String iface, int what, Handler handler) {
          SparseArray<Set<Handler>> ifaceHandlers = mHandlerMap.get(iface);
          if (ifaceHandlers == null) {
              ifaceHandlers = new SparseArray<>();
              mHandlerMap.put(iface, ifaceHandlers);
          }
          Set<Handler> ifaceWhatHandlers = ifaceHandlers.get(what);
          if (ifaceWhatHandlers == null) {
              ifaceWhatHandlers = new ArraySet<>();
              ifaceHandlers.put(what, ifaceWhatHandlers);
          }
          ifaceWhatHandlers.add(handler);
      }
  
      /**
       * Deregister the given |handler|
       * @param iface
       * @param what
       * @param handler
       */
      public synchronized void deregisterHandler(String iface, int what, Handler handler) {
          SparseArray<Set<Handler>> ifaceHandlers = mHandlerMap.get(iface);
          if (ifaceHandlers == null) {
              return;
          }
          Set<Handler> ifaceWhatHandlers = ifaceHandlers.get(what);
          if (ifaceWhatHandlers == null) {
              return;
          }
          ifaceWhatHandlers.remove(handler);
      }
  
      private final Map<String, Boolean> mMonitoringMap = new HashMap<>();
      private boolean isMonitoring(String iface) {
          Boolean val = mMonitoringMap.get(iface);
          if (val == null) {
              return false;
          } else {
              return val.booleanValue();
          }
      }
  
      /**
       * Enable/Disable monitoring for the provided iface.
       *
       * @param iface Name of the iface.
       * @param enabled true to enable, false to disable.
       */
      @VisibleForTesting
      public void setMonitoring(String iface, boolean enabled) {
          mMonitoringMap.put(iface, enabled);
      }
  
      private void setMonitoringNone() {
          for (String iface : mMonitoringMap.keySet()) {
              setMonitoring(iface, false);
          }
      }
  
      /**
       * Start Monitoring for wpa_supplicant events.
       *
       * @param iface Name of iface.
       */
      public synchronized void startMonitoring(String iface) {
          if (mVerboseLoggingEnabled) Log.d(TAG, "startMonitoring(" + iface + ")");
          setMonitoring(iface, true);
          broadcastSupplicantConnectionEvent(iface);
      }
  
      /**
       * Stop Monitoring for wpa_supplicant events.
       *
       * @param iface Name of iface.
       */
      public synchronized void stopMonitoring(String iface) {
          if (mVerboseLoggingEnabled) Log.d(TAG, "stopMonitoring(" + iface + ")");
          setMonitoring(iface, true);
          broadcastSupplicantDisconnectionEvent(iface);
          setMonitoring(iface, false);
      }
  
      /**
       * Stop Monitoring for wpa_supplicant events.
       *
       * TODO: Add unit tests for these once we remove the legacy code.
       */
      public synchronized void stopAllMonitoring() {
          mConnected = false;
          setMonitoringNone();
      }
  
  
      /**
       * Similar functions to Handler#sendMessage that send the message to the registered handler
       * for the given interface and message what.
       * All of these should be called with the WifiMonitor class lock
       */
      private void sendMessage(String iface, int what) {
          sendMessage(iface, Message.obtain(null, what));
      }
  
      private void sendMessage(String iface, int what, Object obj) {
          sendMessage(iface, Message.obtain(null, what, obj));
      }
  
      private void sendMessage(String iface, int what, int arg1) {
          sendMessage(iface, Message.obtain(null, what, arg1, 0));
      }
  
      private void sendMessage(String iface, int what, int arg1, int arg2) {
          sendMessage(iface, Message.obtain(null, what, arg1, arg2));
      }
  
      private void sendMessage(String iface, int what, int arg1, int arg2, Object obj) {
          sendMessage(iface, Message.obtain(null, what, arg1, arg2, obj));
      }
  
      private void sendMessage(String iface, Message message) {
          SparseArray<Set<Handler>> ifaceHandlers = mHandlerMap.get(iface);
          if (iface != null && ifaceHandlers != null) {
              if (isMonitoring(iface)) {
                  Set<Handler> ifaceWhatHandlers = ifaceHandlers.get(message.what);
                  if (ifaceWhatHandlers != null) {
                      for (Handler handler : ifaceWhatHandlers) {
                          if (handler != null) {
                              sendMessage(handler, Message.obtain(message));
                          }
                      }
                  }
              } else {
                  if (mVerboseLoggingEnabled) {
                      Log.d(TAG, "Dropping event because (" + iface + ") is stopped");
                  }
              }
          } else {
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "Sending to all monitors because there's no matching iface");
              }
              for (Map.Entry<String, SparseArray<Set<Handler>>> entry : mHandlerMap.entrySet()) {
                  if (isMonitoring(entry.getKey())) {
                      Set<Handler> ifaceWhatHandlers = entry.getValue().get(message.what);
                      for (Handler handler : ifaceWhatHandlers) {
                          if (handler != null) {
                              sendMessage(handler, Message.obtain(message));
                          }
                      }
                  }
              }
          }
  
          message.recycle();
      }
  
      private void sendMessage(Handler handler, Message message) {
          message.setTarget(handler);
          message.sendToTarget();
      }
  
      /**
       * Broadcast the WPS fail event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param cfgError Configuration error code.
       * @param vendorErrorCode Vendor specific error indication code.
       */
      public void broadcastWpsFailEvent(String iface, int cfgError, int vendorErrorCode) {
          int reason = 0;
          switch(vendorErrorCode) {
              case REASON_TKIP_ONLY_PROHIBITED:
                  sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_TKIP_ONLY_PROHIBITED);
                  return;
              case REASON_WEP_PROHIBITED:
                  sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_WEP_PROHIBITED);
                  return;
              default:
                  reason = vendorErrorCode;
                  break;
          }
          switch(cfgError) {
              case CONFIG_AUTH_FAILURE:
                  sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_AUTH_FAILURE);
                  return;
              case CONFIG_MULTIPLE_PBC_DETECTED:
                  sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_OVERLAP_ERROR);
                  return;
              default:
                  if (reason == 0) {
                      reason = cfgError;
                  }
                  break;
          }
          //For all other errors, return a generic internal error
          sendMessage(iface, WPS_FAIL_EVENT, WifiManager.ERROR, reason);
      }
  
     /**
      * Broadcast the WPS succes event to all the handlers registered for this event.
      *
      * @param iface Name of iface on which this occurred.
      */
      public void broadcastWpsSuccessEvent(String iface) {
          sendMessage(iface, WPS_SUCCESS_EVENT);
      }
  
      /**
       * Broadcast the WPS overlap event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       */
      public void broadcastWpsOverlapEvent(String iface) {
          sendMessage(iface, WPS_OVERLAP_EVENT);
      }
  
      /**
       * Broadcast the WPS timeout event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       */
      public void broadcastWpsTimeoutEvent(String iface) {
          sendMessage(iface, WPS_TIMEOUT_EVENT);
      }
  
      /**
       * Broadcast the ANQP done event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param anqpEvent ANQP result retrieved.
       */
      public void broadcastAnqpDoneEvent(String iface, AnqpEvent anqpEvent) {
          sendMessage(iface, ANQP_DONE_EVENT, anqpEvent);
      }
  
      /**
       * Broadcast the Icon done event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param iconEvent Instance of IconEvent containing the icon data retrieved.
       */
      public void broadcastIconDoneEvent(String iface, IconEvent iconEvent) {
          sendMessage(iface, RX_HS20_ANQP_ICON_EVENT, iconEvent);
      }
  
      /**
       * Broadcast the WNM event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param wnmData Instance of WnmData containing the event data.
       */
      public void broadcastWnmEvent(String iface, WnmData wnmData) {
          sendMessage(iface, HS20_REMEDIATION_EVENT, wnmData);
      }
  
      /**
       * Broadcast the Network identity request event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param networkId ID of the network in wpa_supplicant.
       * @param ssid SSID of the network.
       */
      public void broadcastNetworkIdentityRequestEvent(String iface, int networkId, String ssid) {
          sendMessage(iface, SUP_REQUEST_IDENTITY, 0, networkId, ssid);
      }
  
      /**
       * Broadcast the Network Gsm Sim auth request event to all the handlers registered for this
       * event.
       *
       * @param iface Name of iface on which this occurred.
       * @param networkId ID of the network in wpa_supplicant.
       * @param ssid SSID of the network.
       * @param data Accompanying event data.
       */
      public void broadcastNetworkGsmAuthRequestEvent(String iface, int networkId, String ssid,
                                                      String[] data) {
          sendMessage(iface, SUP_REQUEST_SIM_AUTH,
                  new SimAuthRequestData(networkId, WifiEnterpriseConfig.Eap.SIM, ssid, data));
      }
  
      /**
       * Broadcast the Network Umts Sim auth request event to all the handlers registered for this
       * event.
       *
       * @param iface Name of iface on which this occurred.
       * @param networkId ID of the network in wpa_supplicant.
       * @param ssid SSID of the network.
       * @param data Accompanying event data.
       */
      public void broadcastNetworkUmtsAuthRequestEvent(String iface, int networkId, String ssid,
                                                       String[] data) {
          sendMessage(iface, SUP_REQUEST_SIM_AUTH,
                  new SimAuthRequestData(networkId, WifiEnterpriseConfig.Eap.AKA, ssid, data));
      }
  
      /**
       * Broadcast scan result event to all the handlers registered for this event.
       * @param iface Name of iface on which this occurred.
       */
      public void broadcastScanResultEvent(String iface) {
          sendMessage(iface, SCAN_RESULTS_EVENT);
      }
  
      /**
       * Broadcast pno scan result event to all the handlers registered for this event.
       * @param iface Name of iface on which this occurred.
       */
      public void broadcastPnoScanResultEvent(String iface) {
          sendMessage(iface, PNO_SCAN_RESULTS_EVENT);
      }
  
      /**
       * Broadcast scan failed event to all the handlers registered for this event.
       * @param iface Name of iface on which this occurred.
       */
      public void broadcastScanFailedEvent(String iface) {
          sendMessage(iface, SCAN_FAILED_EVENT);
      }
  
      /**
       * Broadcast the authentication failure event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param reason Reason for authentication failure. This has to be one of the
       *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_NONE},
       *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_TIMEOUT},
       *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_WRONG_PSWD},
       *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_EAP_FAILURE}
       * @param errorCode Error code associated with the authentication failure event.
       *               A value of -1 is used when no error code is reported.
       */
      public void broadcastAuthenticationFailureEvent(String iface, int reason, int errorCode) {
          sendMessage(iface, AUTHENTICATION_FAILURE_EVENT, reason, errorCode);
      }
  
      /**
       * Broadcast the association rejection event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param status Status code for association rejection.
       * @param timedOut Indicates if the association timed out.
       * @param bssid BSSID of the access point from which we received the reject.
       */
      public void broadcastAssociationRejectionEvent(String iface, int status, boolean timedOut,
                                                     String bssid) {
          sendMessage(iface, ASSOCIATION_REJECTION_EVENT, timedOut ? 1 : 0, status, bssid);
      }
  
      /**
       * Broadcast the association success event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param bssid BSSID of the access point.
       */
      public void broadcastAssociatedBssidEvent(String iface, String bssid) {
          sendMessage(iface, ClientModeImpl.CMD_ASSOCIATED_BSSID, 0, 0, bssid);
      }
  
      /**
       * Broadcast the start of association event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param bssid BSSID of the access point.
       */
      public void broadcastTargetBssidEvent(String iface, String bssid) {
          sendMessage(iface, ClientModeImpl.CMD_TARGET_BSSID, 0, 0, bssid);
      }
  
      /**
       * Broadcast the network connection event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param networkId ID of the network in wpa_supplicant.
       * @param bssid BSSID of the access point.
       */
      public void broadcastNetworkConnectionEvent(String iface, int networkId, String bssid) {
          sendMessage(iface, NETWORK_CONNECTION_EVENT, networkId, 0, bssid);
      }
  
      /**
       * Broadcast the network disconnection event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param local Whether the disconnect was locally triggered.
       * @param reason Disconnect reason code.
       * @param bssid BSSID of the access point.
       */
      public void broadcastNetworkDisconnectionEvent(String iface, int local, int reason,
                                                     String bssid) {
          sendMessage(iface, NETWORK_DISCONNECTION_EVENT, local, reason, bssid);
      }
  
      /**
       * Broadcast the supplicant state change event to all the handlers registered for this event.
       *
       * @param iface Name of iface on which this occurred.
       * @param networkId ID of the network in wpa_supplicant.
       * @param bssid BSSID of the access point.
       * @param newSupplicantState New supplicant state.
       */
      public void broadcastSupplicantStateChangeEvent(String iface, int networkId, WifiSsid wifiSsid,
                                                      String bssid,
                                                      SupplicantState newSupplicantState) {
          sendMessage(iface, SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
                  new StateChangeResult(networkId, wifiSsid, bssid, newSupplicantState));
      }
  
      /**
       * Broadcast the connection to wpa_supplicant event to all the handlers registered for
       * this event.
       *
       * @param iface Name of iface on which this occurred.
       */
      public void broadcastSupplicantConnectionEvent(String iface) {
          sendMessage(iface, SUP_CONNECTION_EVENT);
      }
  
      /**
       * Broadcast the loss of connection to wpa_supplicant event to all the handlers registered for
       * this event.
       *
       * @param iface Name of iface on which this occurred.
       */
      public void broadcastSupplicantDisconnectionEvent(String iface) {
          sendMessage(iface, SUP_DISCONNECTION_EVENT);
      }
  }
  