package com.android.server.wifi.hotspot2; import com.android.server.wifi.ScanDetail; import com.android.server.wifi.anqp.ANQPElement; import com.android.server.wifi.anqp.HSConnectionCapabilityElement; import com.android.server.wifi.anqp.HSWanMetricsElement; import com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement; import com.android.server.wifi.hotspot2.pps.HomeSP; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; import static com.android.server.wifi.anqp.Constants.ANQPElementType; import static com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement.IPv4Availability; import static com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement.IPv6Availability; public class PasspointMatchInfo implements Comparable<PasspointMatchInfo> { private final PasspointMatch mPasspointMatch; private final ScanDetail mScanDetail; private final HomeSP mHomeSP; private final int mScore; private static final Map<IPv4Availability, Integer> sIP4Scores = new EnumMap<>(IPv4Availability.class); private static final Map<IPv6Availability, Integer> sIP6Scores = new EnumMap<>(IPv6Availability.class); private static final Map<Integer, Map<Integer, Integer>> sPortScores = new HashMap<>(); private static final int IPPROTO_ICMP = 1; private static final int IPPROTO_TCP = 6; private static final int IPPROTO_UDP = 17; private static final int IPPROTO_ESP = 50; private static final Map<NetworkDetail.Ant, Integer> sAntScores = new HashMap<>(); static { // These are all arbitrarily chosen scores, subject to tuning. sAntScores.put(NetworkDetail.Ant.FreePublic, 4); sAntScores.put(NetworkDetail.Ant.ChargeablePublic, 4); sAntScores.put(NetworkDetail.Ant.PrivateWithGuest, 4); sAntScores.put(NetworkDetail.Ant.Private, 4); sAntScores.put(NetworkDetail.Ant.Personal, 2); sAntScores.put(NetworkDetail.Ant.EmergencyOnly, 2); sAntScores.put(NetworkDetail.Ant.Wildcard, 1); sAntScores.put(NetworkDetail.Ant.TestOrExperimental, 0); sIP4Scores.put(IPv4Availability.NotAvailable, 0); sIP4Scores.put(IPv4Availability.PortRestricted, 1); sIP4Scores.put(IPv4Availability.PortRestrictedAndSingleNAT, 1); sIP4Scores.put(IPv4Availability.PortRestrictedAndDoubleNAT, 1); sIP4Scores.put(IPv4Availability.Unknown, 1); sIP4Scores.put(IPv4Availability.Public, 2); sIP4Scores.put(IPv4Availability.SingleNAT, 2); sIP4Scores.put(IPv4Availability.DoubleNAT, 2); sIP6Scores.put(IPv6Availability.NotAvailable, 0); sIP6Scores.put(IPv6Availability.Reserved, 1); sIP6Scores.put(IPv6Availability.Unknown, 1); sIP6Scores.put(IPv6Availability.Available, 2); Map<Integer, Integer> tcpMap = new HashMap<>(); tcpMap.put(20, 1); tcpMap.put(21, 1); tcpMap.put(22, 3); tcpMap.put(23, 2); tcpMap.put(25, 8); tcpMap.put(26, 8); tcpMap.put(53, 3); tcpMap.put(80, 10); tcpMap.put(110, 6); tcpMap.put(143, 6); tcpMap.put(443, 10); tcpMap.put(993, 6); tcpMap.put(1723, 7); Map<Integer, Integer> udpMap = new HashMap<>(); udpMap.put(53, 10); udpMap.put(500, 7); udpMap.put(5060, 10); udpMap.put(4500, 4); sPortScores.put(IPPROTO_TCP, tcpMap); sPortScores.put(IPPROTO_UDP, udpMap); } public PasspointMatchInfo(PasspointMatch passpointMatch, ScanDetail scanDetail, HomeSP homeSP) { mPasspointMatch = passpointMatch; mScanDetail = scanDetail; mHomeSP = homeSP; int score; if (passpointMatch == PasspointMatch.HomeProvider) { score = 100; } else if (passpointMatch == PasspointMatch.RoamingProvider) { score = 0; } else { score = -1000; // Don't expect to see anything not home or roaming. } if (getNetworkDetail().getHSRelease() != null) { score += getNetworkDetail().getHSRelease() != NetworkDetail.HSRelease.Unknown ? 50 : 0; } if (getNetworkDetail().hasInterworking()) { score += getNetworkDetail().isInternet() ? 20 : -20; } score += (Math.max(200-getNetworkDetail().getStationCount(), 0) * (255-getNetworkDetail().getChannelUtilization()) * getNetworkDetail().getCapacity()) >>> 26; // Gives a value of 23 max capped at 200 stations and max cap 31250 if (getNetworkDetail().hasInterworking()) { score += sAntScores.get(getNetworkDetail().getAnt()); } Map<ANQPElementType, ANQPElement> anqp = getNetworkDetail().getANQPElements(); if (anqp != null) { HSWanMetricsElement wm = (HSWanMetricsElement) anqp.get(ANQPElementType.HSWANMetrics); if (wm != null) { if (wm.getStatus() != HSWanMetricsElement.LinkStatus.Up || wm.isCapped()) { score -= 1000; } else { long scaledSpeed = wm.getDlSpeed() * (255 - wm.getDlLoad()) * 8 + wm.getUlSpeed() * (255 - wm.getUlLoad()) * 2; score += Math.min(scaledSpeed, 255000000L) >>> 23; // Max value is 30 capped at 100Mb/s } } IPAddressTypeAvailabilityElement ipa = (IPAddressTypeAvailabilityElement) anqp.get(ANQPElementType.ANQPIPAddrAvailability); if (ipa != null) { Integer as14 = sIP4Scores.get(ipa.getV4Availability()); Integer as16 = sIP6Scores.get(ipa.getV6Availability()); as14 = as14 != null ? as14 : 1; as16 = as16 != null ? as16 : 1; // Is IPv4 twice as important as IPv6??? score += as14 * 2 + as16; } HSConnectionCapabilityElement cce = (HSConnectionCapabilityElement) anqp.get(ANQPElementType.HSConnCapability); if (cce != null) { score = Math.min(Math.max(protoScore(cce) >> 3, -10), 10); } } mScore = score; } public PasspointMatch getPasspointMatch() { return mPasspointMatch; } public ScanDetail getScanDetail() { return mScanDetail; } public NetworkDetail getNetworkDetail() { return mScanDetail.getNetworkDetail(); } public HomeSP getHomeSP() { return mHomeSP; } public int getScore() { return mScore; } @Override public int compareTo(PasspointMatchInfo that) { return getScore() - that.getScore(); } private static int protoScore(HSConnectionCapabilityElement cce) { int score = 0; for (HSConnectionCapabilityElement.ProtocolTuple tuple : cce.getStatusList()) { int sign = tuple.getStatus() == HSConnectionCapabilityElement.ProtoStatus.Open ? 1 : -1; int elementScore = 1; if (tuple.getProtocol() == IPPROTO_ICMP) { elementScore = 1; } else if (tuple.getProtocol() == IPPROTO_ESP) { elementScore = 5; } else { Map<Integer, Integer> protoMap = sPortScores.get(tuple.getProtocol()); if (protoMap != null) { Integer portScore = protoMap.get(tuple.getPort()); elementScore = portScore != null ? portScore : 0; } } score += elementScore * sign; } return score; } @Override public boolean equals(Object thatObject) { if (this == thatObject) { return true; } if (thatObject == null || getClass() != thatObject.getClass()) { return false; } PasspointMatchInfo that = (PasspointMatchInfo)thatObject; return getNetworkDetail().equals(that.getNetworkDetail()) && getHomeSP().equals(that.getHomeSP()) && getPasspointMatch().equals(that.getPasspointMatch()); } @Override public int hashCode() { int result = mPasspointMatch != null ? mPasspointMatch.hashCode() : 0; result = 31 * result + getNetworkDetail().hashCode(); result = 31 * result + (mHomeSP != null ? mHomeSP.hashCode() : 0); return result; } @Override public String toString() { return "PasspointMatchInfo{" + ", mPasspointMatch=" + mPasspointMatch + ", mNetworkInfo=" + getNetworkDetail().getSSID() + ", mHomeSP=" + mHomeSP.getFQDN() + '}'; } }