package com.android.server.wifi.hotspot2.pps;
import android.util.Log;
import com.android.server.wifi.SIMAccessor;
import com.android.server.wifi.anqp.ANQPElement;
import com.android.server.wifi.anqp.CellularNetwork;
import com.android.server.wifi.anqp.DomainNameElement;
import com.android.server.wifi.anqp.NAIRealmElement;
import com.android.server.wifi.anqp.RoamingConsortiumElement;
import com.android.server.wifi.anqp.ThreeGPPNetworkElement;
import com.android.server.wifi.hotspot2.AuthMatch;
import com.android.server.wifi.hotspot2.NetworkDetail;
import com.android.server.wifi.hotspot2.PasspointMatch;
import com.android.server.wifi.hotspot2.Utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.android.server.wifi.anqp.Constants.ANQPElementType;
public class HomeSP {
private final Map<String, Long> mSSIDs; // SSID, HESSID, [0,N]
private final String mFQDN;
private final DomainMatcher mDomainMatcher;
private final Set<String> mOtherHomePartners;
private final HashSet<Long> mRoamingConsortiums; // [0,N]
private final Set<Long> mMatchAnyOIs; // [0,N]
private final List<Long> mMatchAllOIs; // [0,N]
private final Credential mCredential;
// Informational:
private final String mFriendlyName; // [1]
private final String mIconURL; // [0,1]
public HomeSP(Map<String, Long> ssidMap,
/*@NotNull*/ String fqdn,
/*@NotNull*/ HashSet<Long> roamingConsortiums,
/*@NotNull*/ Set<String> otherHomePartners,
/*@NotNull*/ Set<Long> matchAnyOIs,
/*@NotNull*/ List<Long> matchAllOIs,
String friendlyName,
String iconURL,
Credential credential) {
mSSIDs = ssidMap;
List<List<String>> otherPartners = new ArrayList<>(otherHomePartners.size());
for (String otherPartner : otherHomePartners) {
otherPartners.add(Utils.splitDomain(otherPartner));
}
mOtherHomePartners = otherHomePartners;
mFQDN = fqdn;
mDomainMatcher = new DomainMatcher(Utils.splitDomain(fqdn), otherPartners);
mRoamingConsortiums = roamingConsortiums;
mMatchAnyOIs = matchAnyOIs;
mMatchAllOIs = matchAllOIs;
mFriendlyName = friendlyName;
mIconURL = iconURL;
mCredential = credential;
}
public HomeSP getClone(String password) {
if (getCredential().hasDisregardPassword()) {
return new HomeSP(mSSIDs,
mFQDN,
mRoamingConsortiums,
mOtherHomePartners,
mMatchAnyOIs,
mMatchAllOIs,
mFriendlyName,
mIconURL,
new Credential(mCredential, password));
}
else {
return this;
}
}
public PasspointMatch match(NetworkDetail networkDetail,
Map<ANQPElementType, ANQPElement> anqpElementMap,
SIMAccessor simAccessor) {
List<String> imsis = simAccessor.getMatchingImsis(mCredential.getImsi());
PasspointMatch spMatch = matchSP(networkDetail, anqpElementMap, imsis);
if (spMatch == PasspointMatch.Incomplete || spMatch == PasspointMatch.Declined) {
return spMatch;
}
if (imsiMatch(imsis, (ThreeGPPNetworkElement)
anqpElementMap.get(ANQPElementType.ANQP3GPPNetwork)) != null) {
// PLMN match, promote sp match to roaming if necessary.
return spMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider : spMatch;
}
NAIRealmElement naiRealmElement =
(NAIRealmElement) anqpElementMap.get(ANQPElementType.ANQPNAIRealm);
int authMatch = naiRealmElement != null ?
naiRealmElement.match(mCredential) :
AuthMatch.Indeterminate;
Log.d(Utils.hs2LogTag(getClass()), networkDetail.toKeyString() + " match on " + mFQDN +
": " + spMatch + ", auth " + AuthMatch.toString(authMatch));
if (authMatch == AuthMatch.None) {
// Distinct auth mismatch, demote authentication.
return PasspointMatch.None;
}
else if ((authMatch & AuthMatch.Realm) == 0) {
// No realm match, return sp match as is.
return spMatch;
}
else {
// Realm match, promote sp match to roaming if necessary.
return spMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider : spMatch;
}
}
public PasspointMatch matchSP(NetworkDetail networkDetail,
Map<ANQPElementType, ANQPElement> anqpElementMap,
List<String> imsis) {
if (mSSIDs.containsKey(networkDetail.getSSID())) {
Long hessid = mSSIDs.get(networkDetail.getSSID());
if (hessid == null || networkDetail.getHESSID() == hessid) {
Log.d(Utils.hs2LogTag(getClass()), "match SSID");
return PasspointMatch.HomeProvider;
}
}
Set<Long> anOIs = new HashSet<>();
if (networkDetail.getRoamingConsortiums() != null) {
for (long oi : networkDetail.getRoamingConsortiums()) {
anOIs.add(oi);
}
}
RoamingConsortiumElement rcElement = anqpElementMap != null ?
(RoamingConsortiumElement) anqpElementMap.get(ANQPElementType.ANQPRoamingConsortium)
: null;
if (rcElement != null) {
anOIs.addAll(rcElement.getOIs());
}
// It may seem reasonable to check for home provider match prior to checking for roaming
// relationship, but it is possible to avoid an ANQP query if it turns out that the
// "match all" rule fails based only on beacon info only.
boolean roamingMatch = false;
if (!mMatchAllOIs.isEmpty()) {
boolean matchesAll = true;
for (long spOI : mMatchAllOIs) {
if (!anOIs.contains(spOI)) {
matchesAll = false;
break;
}
}
if (matchesAll) {
roamingMatch = true;
}
else {
if (anqpElementMap != null || networkDetail.getAnqpOICount() == 0) {
return PasspointMatch.Declined;
}
else {
return PasspointMatch.Incomplete;
}
}
}
if (!roamingMatch &&
(!Collections.disjoint(mMatchAnyOIs, anOIs) ||
!Collections.disjoint(mRoamingConsortiums, anOIs))) {
roamingMatch = true;
}
if (anqpElementMap == null) {
return PasspointMatch.Incomplete;
}
DomainNameElement domainNameElement =
(DomainNameElement) anqpElementMap.get(ANQPElementType.ANQPDomName);
if (domainNameElement != null) {
for (String domain : domainNameElement.getDomains()) {
List<String> anLabels = Utils.splitDomain(domain);
DomainMatcher.Match match = mDomainMatcher.isSubDomain(anLabels);
if (match != DomainMatcher.Match.None) {
return PasspointMatch.HomeProvider;
}
if (imsiMatch(imsis, anLabels) != null) {
return PasspointMatch.HomeProvider;
}
}
}
return roamingMatch ? PasspointMatch.RoamingProvider : PasspointMatch.None;
}
private String imsiMatch(List<String> imsis, ThreeGPPNetworkElement plmnElement) {
if (imsis == null || plmnElement == null || plmnElement.getPlmns().isEmpty()) {
return null;
}
for (CellularNetwork network : plmnElement.getPlmns()) {
for (String mccMnc : network) {
String imsi = imsiMatch(imsis, mccMnc);
if (imsi != null) {
return imsi;
}
}
}
return null;
}
private String imsiMatch(List<String> imsis, List<String> fqdn) {
if (imsis == null) {
return null;
}
String mccMnc = Utils.getMccMnc(fqdn);
return mccMnc != null ? imsiMatch(imsis, mccMnc) : null;
}
private String imsiMatch(List<String> imsis, String mccMnc) {
if (mCredential.getImsi().matchesMccMnc(mccMnc)) {
for (String imsi : imsis) {
if (imsi.startsWith(mccMnc)) {
return imsi;
}
}
}
return null;
}
public String getFQDN() { return mFQDN; }
public String getFriendlyName() { return mFriendlyName; }
public HashSet<Long> getRoamingConsortiums() { return mRoamingConsortiums; }
public Credential getCredential() { return mCredential; }
public Map<String, Long> getSSIDs() {
return mSSIDs;
}
public Collection<String> getOtherHomePartners() {
return mOtherHomePartners;
}
public Set<Long> getMatchAnyOIs() {
return mMatchAnyOIs;
}
public List<Long> getMatchAllOIs() {
return mMatchAllOIs;
}
public String getIconURL() {
return mIconURL;
}
public boolean deepEquals(HomeSP other) {
return mFQDN.equals(other.mFQDN) &&
mSSIDs.equals(other.mSSIDs) &&
mOtherHomePartners.equals(other.mOtherHomePartners) &&
mRoamingConsortiums.equals(other.mRoamingConsortiums) &&
mMatchAnyOIs.equals(other.mMatchAnyOIs) &&
mMatchAllOIs.equals(other.mMatchAllOIs) &&
mFriendlyName.equals(other.mFriendlyName) &&
Utils.compare(mIconURL, other.mIconURL) == 0 &&
mCredential.equals(other.mCredential);
}
@Override
public boolean equals(Object thatObject) {
if (this == thatObject) {
return true;
} else if (thatObject == null || getClass() != thatObject.getClass()) {
return false;
}
HomeSP that = (HomeSP) thatObject;
return mFQDN.equals(that.mFQDN);
}
@Override
public int hashCode() {
return mFQDN.hashCode();
}
@Override
public String toString() {
return "HomeSP{" +
"mSSIDs=" + mSSIDs +
", mFQDN='" + mFQDN + '\'' +
", mDomainMatcher=" + mDomainMatcher +
", mRoamingConsortiums={" + Utils.roamingConsortiumsToString(mRoamingConsortiums) +
'}' +
", mMatchAnyOIs={" + Utils.roamingConsortiumsToString(mMatchAnyOIs) + '}' +
", mMatchAllOIs={" + Utils.roamingConsortiumsToString(mMatchAllOIs) + '}' +
", mCredential=" + mCredential +
", mFriendlyName='" + mFriendlyName + '\'' +
", mIconURL='" + mIconURL + '\'' +
'}';
}
}