content: /*
   * Copyright (C) 2016 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.hotspot2;
  
  import static com.android.server.wifi.hotspot2.Utils.isCarrierEapMethod;
  
  import com.android.server.wifi.IMSIParameter;
  import com.android.server.wifi.hotspot2.anqp.CellularNetwork;
  import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
  import com.android.server.wifi.hotspot2.anqp.NAIRealmData;
  import com.android.server.wifi.hotspot2.anqp.NAIRealmElement;
  import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement;
  import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
  import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
  import com.android.server.wifi.hotspot2.anqp.eap.EAPMethod;
  
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * Utility class for providing matching functions against ANQP elements.
   */
  public class ANQPMatcher {
      /**
       * Match the domain names in the ANQP element against the provider's FQDN and SIM credential.
       * The Domain Name ANQP element might contain domains for 3GPP network (e.g.
       * wlan.mnc*.mcc*.3gppnetwork.org), so we should match that against the provider's SIM
       * credential if one is provided.
       *
       * @param element The Domain Name ANQP element
       * @param fqdn The FQDN to compare against
       * @param imsiParam The IMSI parameter of the provider
       * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
       *                    IMSI parameter
       * @return true if a match is found
       */
      public static boolean matchDomainName(DomainNameElement element, String fqdn,
              IMSIParameter imsiParam, List<String> simImsiList) {
          if (element == null) {
              return false;
          }
  
          for (String domain : element.getDomains()) {
              if (DomainMatcher.arg2SubdomainOfArg1(fqdn, domain)) {
                  return true;
              }
  
              // Try to retrieve the MCC-MNC string from the domain (for 3GPP network domain) and
              // match against the provider's SIM credential.
              if (matchMccMnc(Utils.getMccMnc(Utils.splitDomain(domain)), imsiParam, simImsiList)) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Match the roaming consortium OIs in the ANQP element against the roaming consortium OIs
       * of a provider.
       *
       * @param element The Roaming Consortium ANQP element
       * @param providerOIs The roaming consortium OIs of the provider
       * @param matchAll Indicates if a match with all OIs must be done
       * @return true if a match is found
       */
      public static boolean matchRoamingConsortium(RoamingConsortiumElement element,
              long[] providerOIs, boolean matchAll) {
          if (element == null) {
              return false;
          }
          if (providerOIs == null) {
              return false;
          }
          List<Long> rcOIs = element.getOIs();
          for (long oi : providerOIs) {
              if (rcOIs.contains(oi)) {
                  if (!matchAll) {
                      return true;
                  }
              } else if (matchAll) {
                  return false;
              }
          }
          return matchAll;
      }
  
      /**
       * Match the NAI realm in the ANQP element against the realm and authentication method of
       * a provider.
       *
       * @param element The NAI Realm ANQP element
       * @param realm The realm of the provider's credential
       * @return an integer indicating the match status
       */
      public static boolean matchNAIRealm(NAIRealmElement element, String realm) {
          if (element == null || element.getRealmDataList().isEmpty()) {
              return false;
          }
  
          for (NAIRealmData realmData : element.getRealmDataList()) {
              if (matchNAIRealmData(realmData, realm)) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Get a EAP-Method from a corresponding NAI realm that has one of them (EAP-SIM/AKA/AKA)'.
       *
       * @param realm a realm of the provider's credential.
       * @param element The NAI Realm ANQP element
       * @return a EAP Method (EAP-SIM/AKA/AKA') from matching NAI realm, {@code -1} otherwise.
       */
      public static int getCarrierEapMethodFromMatchingNAIRealm(String realm,
              NAIRealmElement element) {
          if (element == null || element.getRealmDataList().isEmpty()) {
              return -1;
          }
  
          for (NAIRealmData realmData : element.getRealmDataList()) {
              int eapMethodID = getEapMethodForNAIRealmWithCarrier(realm, realmData);
              if (eapMethodID != -1) {
                  return eapMethodID;
              }
          }
          return -1;
      }
  
      /**
       * Match the 3GPP Network in the ANQP element against the SIM credential of a provider.
       *
       * @param element 3GPP Network ANQP element
       * @param imsiParam The IMSI parameter of the provider's SIM credential
       * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
       *                    IMSI parameter
       * @return true if a matched is found
       */
      public static  boolean matchThreeGPPNetwork(ThreeGPPNetworkElement element,
              IMSIParameter imsiParam, List<String> simImsiList) {
          if (element == null) {
              return false;
          }
          for (CellularNetwork network : element.getNetworks()) {
              if (matchCellularNetwork(network, imsiParam, simImsiList)) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Match the given NAI Realm data against the realm and authentication method of a provider.
       *
       * @param realmData The NAI Realm data
       * @param realm The realm of the provider's credential
       * @return true if a match is found
       */
      private static boolean matchNAIRealmData(NAIRealmData realmData, String realm) {
          // Check for realm domain name match.
          for (String realmStr : realmData.getRealms()) {
              if (DomainMatcher.arg2SubdomainOfArg1(realm, realmStr)) {
                  return true;
              }
          }
          return false;
      }
  
      private static int getEapMethodForNAIRealmWithCarrier(String realm,
              NAIRealmData realmData) {
          int realmMatch = AuthMatch.NONE;
  
          for (String realmStr : realmData.getRealms()) {
              if (DomainMatcher.arg2SubdomainOfArg1(realm, realmStr)) {
                  realmMatch = AuthMatch.REALM;
                  break;
              }
          }
  
          if (realmMatch == AuthMatch.NONE) {
              return -1;
          }
  
          for (EAPMethod eapMethod : realmData.getEAPMethods()) {
              if (isCarrierEapMethod(eapMethod.getEAPMethodID())) {
                  return eapMethod.getEAPMethodID();
              }
          }
          return -1;
      }
  
      /**
       * Match the given EAPMethod against the authentication method of a provider.
       *
       * @param method The EAP Method
       * @param eapMethodID The EAP Method ID of the provider's credential
       * @param authParam The authentication parameter of the provider's credential
       * @return an integer indicating the match status
       */
      private static int matchEAPMethod(EAPMethod method, int eapMethodID, AuthParam authParam) {
          if (method.getEAPMethodID() != eapMethodID) {
              return AuthMatch.NONE;
          }
          // Check for authentication parameter match.
          if (authParam != null) {
              Map<Integer, Set<AuthParam>> authParams = method.getAuthParams();
              if (authParams.isEmpty()) {
                  // no auth methods to match
                  return AuthMatch.METHOD;
              }
              Set<AuthParam> paramSet = authParams.get(authParam.getAuthTypeID());
              if (paramSet == null || !paramSet.contains(authParam)) {
                  return AuthMatch.NONE;
              }
              return AuthMatch.METHOD_PARAM;
          }
          return AuthMatch.METHOD;
      }
  
      /**
       * Match a cellular network information in the 3GPP Network ANQP element against the SIM
       * credential of a provider.
       *
       * @param network The cellular network that contained list of PLMNs
       * @param imsiParam IMSI parameter of the provider
       * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
       *                    IMSI parameter
       * @return true if a match is found
       */
      private static boolean matchCellularNetwork(CellularNetwork network, IMSIParameter imsiParam,
              List<String> simImsiList) {
          for (String plmn : network.getPlmns()) {
              if (matchMccMnc(plmn, imsiParam, simImsiList)) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Match a MCC-MNC against the SIM credential of a provider.
       *
       * @param mccMnc The string containing MCC-MNC
       * @param imsiParam The IMSI parameter of the provider
       * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
       *                    IMSI parameter
       * @return true if a match is found
       */
      private static boolean matchMccMnc(String mccMnc, IMSIParameter imsiParam,
              List<String> simImsiList) {
          if (imsiParam == null || simImsiList == null) {
              return false;
          }
          // Match against the IMSI parameter in the provider.
          if (!imsiParam.matchesMccMnc(mccMnc)) {
              return false;
          }
          // Additional check for verifying the match with IMSIs from the SIM cards, since the IMSI
          // parameter might not contain the full 6-digit MCC MNC (e.g. IMSI parameter is an IMSI
          // prefix that contained less than 6-digit of numbers "12345*").
          for (String imsi : simImsiList) {
              if (imsi.startsWith(mccMnc)) {
                  return true;
              }
          }
          return false;
      }
  }
  