1 package com.android.server.wifi.hotspot2; 2 3 import com.android.server.wifi.ScanDetail; 4 import com.android.server.wifi.anqp.ANQPElement; 5 import com.android.server.wifi.anqp.HSConnectionCapabilityElement; 6 import com.android.server.wifi.anqp.HSWanMetricsElement; 7 import com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement; 8 import com.android.server.wifi.hotspot2.pps.HomeSP; 9 10 import java.util.EnumMap; 11 import java.util.HashMap; 12 import java.util.Map; 13 14 import static com.android.server.wifi.anqp.Constants.ANQPElementType; 15 import static com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement.IPv4Availability; 16 import static com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement.IPv6Availability; 17 18 public class PasspointMatchInfo implements Comparable<PasspointMatchInfo> { 19 private final PasspointMatch mPasspointMatch; 20 private final ScanDetail mScanDetail; 21 private final HomeSP mHomeSP; 22 private final int mScore; 23 24 private static final Map<IPv4Availability, Integer> sIP4Scores = 25 new EnumMap<>(IPv4Availability.class); 26 private static final Map<IPv6Availability, Integer> sIP6Scores = 27 new EnumMap<>(IPv6Availability.class); 28 29 private static final Map<Integer, Map<Integer, Integer>> sPortScores = new HashMap<>(); 30 31 private static final int IPPROTO_ICMP = 1; 32 private static final int IPPROTO_TCP = 6; 33 private static final int IPPROTO_UDP = 17; 34 private static final int IPPROTO_ESP = 50; 35 private static final Map<NetworkDetail.Ant, Integer> sAntScores = new HashMap<>(); 36 37 static { 38 // These are all arbitrarily chosen scores, subject to tuning. 39 40 sAntScores.put(NetworkDetail.Ant.FreePublic, 4); 41 sAntScores.put(NetworkDetail.Ant.ChargeablePublic, 4); 42 sAntScores.put(NetworkDetail.Ant.PrivateWithGuest, 4); 43 sAntScores.put(NetworkDetail.Ant.Private, 4); 44 sAntScores.put(NetworkDetail.Ant.Personal, 2); 45 sAntScores.put(NetworkDetail.Ant.EmergencyOnly, 2); 46 sAntScores.put(NetworkDetail.Ant.Wildcard, 1); 47 sAntScores.put(NetworkDetail.Ant.TestOrExperimental, 0); 48 49 sIP4Scores.put(IPv4Availability.NotAvailable, 0); 50 sIP4Scores.put(IPv4Availability.PortRestricted, 1); 51 sIP4Scores.put(IPv4Availability.PortRestrictedAndSingleNAT, 1); 52 sIP4Scores.put(IPv4Availability.PortRestrictedAndDoubleNAT, 1); 53 sIP4Scores.put(IPv4Availability.Unknown, 1); 54 sIP4Scores.put(IPv4Availability.Public, 2); 55 sIP4Scores.put(IPv4Availability.SingleNAT, 2); 56 sIP4Scores.put(IPv4Availability.DoubleNAT, 2); 57 58 sIP6Scores.put(IPv6Availability.NotAvailable, 0); 59 sIP6Scores.put(IPv6Availability.Reserved, 1); 60 sIP6Scores.put(IPv6Availability.Unknown, 1); 61 sIP6Scores.put(IPv6Availability.Available, 2); 62 63 Map<Integer, Integer> tcpMap = new HashMap<>(); 64 tcpMap.put(20, 1); 65 tcpMap.put(21, 1); 66 tcpMap.put(22, 3); 67 tcpMap.put(23, 2); 68 tcpMap.put(25, 8); 69 tcpMap.put(26, 8); 70 tcpMap.put(53, 3); 71 tcpMap.put(80, 10); 72 tcpMap.put(110, 6); 73 tcpMap.put(143, 6); 74 tcpMap.put(443, 10); 75 tcpMap.put(993, 6); 76 tcpMap.put(1723, 7); 77 78 Map<Integer, Integer> udpMap = new HashMap<>(); 79 udpMap.put(53, 10); 80 udpMap.put(500, 7); 81 udpMap.put(5060, 10); 82 udpMap.put(4500, 4); 83 84 sPortScores.put(IPPROTO_TCP, tcpMap); 85 sPortScores.put(IPPROTO_UDP, udpMap); 86 } 87 88 89 public PasspointMatchInfo(PasspointMatch passpointMatch, 90 ScanDetail scanDetail, HomeSP homeSP) { 91 mPasspointMatch = passpointMatch; 92 mScanDetail = scanDetail; 93 mHomeSP = homeSP; 94 95 int score; 96 if (passpointMatch == PasspointMatch.HomeProvider) { 97 score = 100; 98 } 99 else if (passpointMatch == PasspointMatch.RoamingProvider) { 100 score = 0; 101 } 102 else { 103 score = -1000; // Don't expect to see anything not home or roaming. 104 } 105 106 if (getNetworkDetail().getHSRelease() != null) { 107 score += getNetworkDetail().getHSRelease() != NetworkDetail.HSRelease.Unknown ? 50 : 0; 108 } 109 110 if (getNetworkDetail().hasInterworking()) { 111 score += getNetworkDetail().isInternet() ? 20 : -20; 112 } 113 114 score += (Math.max(200-getNetworkDetail().getStationCount(), 0) * 115 (255-getNetworkDetail().getChannelUtilization()) * 116 getNetworkDetail().getCapacity()) >>> 26; 117 // Gives a value of 23 max capped at 200 stations and max cap 31250 118 119 if (getNetworkDetail().hasInterworking()) { 120 score += sAntScores.get(getNetworkDetail().getAnt()); 121 } 122 123 Map<ANQPElementType, ANQPElement> anqp = getNetworkDetail().getANQPElements(); 124 125 if (anqp != null) { 126 HSWanMetricsElement wm = (HSWanMetricsElement) anqp.get(ANQPElementType.HSWANMetrics); 127 128 if (wm != null) { 129 if (wm.getStatus() != HSWanMetricsElement.LinkStatus.Up || wm.isCapped()) { 130 score -= 1000; 131 } else { 132 long scaledSpeed = 133 wm.getDlSpeed() * (255 - wm.getDlLoad()) * 8 + 134 wm.getUlSpeed() * (255 - wm.getUlLoad()) * 2; 135 score += Math.min(scaledSpeed, 255000000L) >>> 23; 136 // Max value is 30 capped at 100Mb/s 137 } 138 } 139 140 IPAddressTypeAvailabilityElement ipa = 141 (IPAddressTypeAvailabilityElement) anqp.get(ANQPElementType.ANQPIPAddrAvailability); 142 143 if (ipa != null) { 144 Integer as14 = sIP4Scores.get(ipa.getV4Availability()); 145 Integer as16 = sIP6Scores.get(ipa.getV6Availability()); 146 as14 = as14 != null ? as14 : 1; 147 as16 = as16 != null ? as16 : 1; 148 // Is IPv4 twice as important as IPv6??? 149 score += as14 * 2 + as16; 150 } 151 152 HSConnectionCapabilityElement cce = 153 (HSConnectionCapabilityElement) anqp.get(ANQPElementType.HSConnCapability); 154 155 if (cce != null) { 156 score = Math.min(Math.max(protoScore(cce) >> 3, -10), 10); 157 } 158 } 159 160 mScore = score; 161 } 162 163 public PasspointMatch getPasspointMatch() { 164 return mPasspointMatch; 165 } 166 167 public ScanDetail getScanDetail() { 168 return mScanDetail; 169 } 170 171 public NetworkDetail getNetworkDetail() { 172 return mScanDetail.getNetworkDetail(); 173 } 174 175 176 public HomeSP getHomeSP() { 177 return mHomeSP; 178 } 179 180 public int getScore() { 181 return mScore; 182 } 183 184 @Override 185 public int compareTo(PasspointMatchInfo that) { 186 return getScore() - that.getScore(); 187 } 188 189 private static int protoScore(HSConnectionCapabilityElement cce) { 190 int score = 0; 191 for (HSConnectionCapabilityElement.ProtocolTuple tuple : cce.getStatusList()) { 192 int sign = tuple.getStatus() == HSConnectionCapabilityElement.ProtoStatus.Open ? 193 1 : -1; 194 195 int elementScore = 1; 196 if (tuple.getProtocol() == IPPROTO_ICMP) { 197 elementScore = 1; 198 } 199 else if (tuple.getProtocol() == IPPROTO_ESP) { 200 elementScore = 5; 201 } 202 else { 203 Map<Integer, Integer> protoMap = sPortScores.get(tuple.getProtocol()); 204 if (protoMap != null) { 205 Integer portScore = protoMap.get(tuple.getPort()); 206 elementScore = portScore != null ? portScore : 0; 207 } 208 } 209 score += elementScore * sign; 210 } 211 return score; 212 } 213 214 @Override 215 public boolean equals(Object thatObject) { 216 if (this == thatObject) { 217 return true; 218 } 219 if (thatObject == null || getClass() != thatObject.getClass()) { 220 return false; 221 } 222 223 PasspointMatchInfo that = (PasspointMatchInfo)thatObject; 224 225 return getNetworkDetail().equals(that.getNetworkDetail()) && 226 getHomeSP().equals(that.getHomeSP()) && 227 getPasspointMatch().equals(that.getPasspointMatch()); 228 } 229 230 @Override 231 public int hashCode() { 232 int result = mPasspointMatch != null ? mPasspointMatch.hashCode() : 0; 233 result = 31 * result + getNetworkDetail().hashCode(); 234 result = 31 * result + (mHomeSP != null ? mHomeSP.hashCode() : 0); 235 return result; 236 } 237 238 @Override 239 public String toString() { 240 return "PasspointMatchInfo{" + 241 ", mPasspointMatch=" + mPasspointMatch + 242 ", mNetworkInfo=" + getNetworkDetail().getSSID() + 243 ", mHomeSP=" + mHomeSP.getFQDN() + 244 '}'; 245 } 246 } 247