1package com.android.server.wifi.hotspot2.pps; 2 3import android.util.Log; 4 5import com.android.server.wifi.SIMAccessor; 6import com.android.server.wifi.anqp.ANQPElement; 7import com.android.server.wifi.anqp.CellularNetwork; 8import com.android.server.wifi.anqp.DomainNameElement; 9import com.android.server.wifi.anqp.NAIRealmElement; 10import com.android.server.wifi.anqp.RoamingConsortiumElement; 11import com.android.server.wifi.anqp.ThreeGPPNetworkElement; 12import com.android.server.wifi.hotspot2.AuthMatch; 13import com.android.server.wifi.hotspot2.NetworkDetail; 14import com.android.server.wifi.hotspot2.PasspointMatch; 15import com.android.server.wifi.hotspot2.Utils; 16 17import java.util.ArrayList; 18import java.util.Collection; 19import java.util.Collections; 20import java.util.HashSet; 21import java.util.List; 22import java.util.Map; 23import java.util.Set; 24 25import static com.android.server.wifi.anqp.Constants.ANQPElementType; 26 27public class HomeSP { 28 private final Map<String, Long> mSSIDs; // SSID, HESSID, [0,N] 29 private final String mFQDN; 30 private final DomainMatcher mDomainMatcher; 31 private final Set<String> mOtherHomePartners; 32 private final HashSet<Long> mRoamingConsortiums; // [0,N] 33 private final Set<Long> mMatchAnyOIs; // [0,N] 34 private final List<Long> mMatchAllOIs; // [0,N] 35 36 private final Credential mCredential; 37 38 // Informational: 39 private final String mFriendlyName; // [1] 40 private final String mIconURL; // [0,1] 41 42 public HomeSP(Map<String, Long> ssidMap, 43 /*@NotNull*/ String fqdn, 44 /*@NotNull*/ HashSet<Long> roamingConsortiums, 45 /*@NotNull*/ Set<String> otherHomePartners, 46 /*@NotNull*/ Set<Long> matchAnyOIs, 47 /*@NotNull*/ List<Long> matchAllOIs, 48 String friendlyName, 49 String iconURL, 50 Credential credential) { 51 52 mSSIDs = ssidMap; 53 List<List<String>> otherPartners = new ArrayList<>(otherHomePartners.size()); 54 for (String otherPartner : otherHomePartners) { 55 otherPartners.add(Utils.splitDomain(otherPartner)); 56 } 57 mOtherHomePartners = otherHomePartners; 58 mFQDN = fqdn; 59 mDomainMatcher = new DomainMatcher(Utils.splitDomain(fqdn), otherPartners); 60 mRoamingConsortiums = roamingConsortiums; 61 mMatchAnyOIs = matchAnyOIs; 62 mMatchAllOIs = matchAllOIs; 63 mFriendlyName = friendlyName; 64 mIconURL = iconURL; 65 mCredential = credential; 66 } 67 68 public HomeSP getClone(String password) { 69 if (getCredential().hasDisregardPassword()) { 70 return new HomeSP(mSSIDs, 71 mFQDN, 72 mRoamingConsortiums, 73 mOtherHomePartners, 74 mMatchAnyOIs, 75 mMatchAllOIs, 76 mFriendlyName, 77 mIconURL, 78 new Credential(mCredential, password)); 79 } 80 else { 81 return this; 82 } 83 } 84 85 public PasspointMatch match(NetworkDetail networkDetail, 86 Map<ANQPElementType, ANQPElement> anqpElementMap, 87 SIMAccessor simAccessor) { 88 89 List<String> imsis = simAccessor.getMatchingImsis(mCredential.getImsi()); 90 91 PasspointMatch spMatch = matchSP(networkDetail, anqpElementMap, imsis); 92 93 if (spMatch == PasspointMatch.Incomplete || spMatch == PasspointMatch.Declined) { 94 return spMatch; 95 } 96 97 if (imsiMatch(imsis, (ThreeGPPNetworkElement) 98 anqpElementMap.get(ANQPElementType.ANQP3GPPNetwork)) != null) { 99 // PLMN match, promote sp match to roaming if necessary. 100 return spMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider : spMatch; 101 } 102 103 NAIRealmElement naiRealmElement = 104 (NAIRealmElement) anqpElementMap.get(ANQPElementType.ANQPNAIRealm); 105 106 int authMatch = naiRealmElement != null ? 107 naiRealmElement.match(mCredential) : 108 AuthMatch.Indeterminate; 109 110 Log.d(Utils.hs2LogTag(getClass()), networkDetail.toKeyString() + " match on " + mFQDN + 111 ": " + spMatch + ", auth " + AuthMatch.toString(authMatch)); 112 113 if (authMatch == AuthMatch.None) { 114 // Distinct auth mismatch, demote authentication. 115 return PasspointMatch.None; 116 } 117 else if ((authMatch & AuthMatch.Realm) == 0) { 118 // No realm match, return sp match as is. 119 return spMatch; 120 } 121 else { 122 // Realm match, promote sp match to roaming if necessary. 123 return spMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider : spMatch; 124 } 125 } 126 127 public PasspointMatch matchSP(NetworkDetail networkDetail, 128 Map<ANQPElementType, ANQPElement> anqpElementMap, 129 List<String> imsis) { 130 131 if (mSSIDs.containsKey(networkDetail.getSSID())) { 132 Long hessid = mSSIDs.get(networkDetail.getSSID()); 133 if (hessid == null || networkDetail.getHESSID() == hessid) { 134 Log.d(Utils.hs2LogTag(getClass()), "match SSID"); 135 return PasspointMatch.HomeProvider; 136 } 137 } 138 139 Set<Long> anOIs = new HashSet<>(); 140 141 if (networkDetail.getRoamingConsortiums() != null) { 142 for (long oi : networkDetail.getRoamingConsortiums()) { 143 anOIs.add(oi); 144 } 145 } 146 RoamingConsortiumElement rcElement = anqpElementMap != null ? 147 (RoamingConsortiumElement) anqpElementMap.get(ANQPElementType.ANQPRoamingConsortium) 148 : null; 149 if (rcElement != null) { 150 anOIs.addAll(rcElement.getOIs()); 151 } 152 153 // It may seem reasonable to check for home provider match prior to checking for roaming 154 // relationship, but it is possible to avoid an ANQP query if it turns out that the 155 // "match all" rule fails based only on beacon info only. 156 boolean roamingMatch = false; 157 158 if (!mMatchAllOIs.isEmpty()) { 159 boolean matchesAll = true; 160 161 for (long spOI : mMatchAllOIs) { 162 if (!anOIs.contains(spOI)) { 163 matchesAll = false; 164 break; 165 } 166 } 167 if (matchesAll) { 168 roamingMatch = true; 169 } 170 else { 171 if (anqpElementMap != null || networkDetail.getAnqpOICount() == 0) { 172 return PasspointMatch.Declined; 173 } 174 else { 175 return PasspointMatch.Incomplete; 176 } 177 } 178 } 179 180 if (!roamingMatch && 181 (!Collections.disjoint(mMatchAnyOIs, anOIs) || 182 !Collections.disjoint(mRoamingConsortiums, anOIs))) { 183 roamingMatch = true; 184 } 185 186 if (anqpElementMap == null) { 187 return PasspointMatch.Incomplete; 188 } 189 190 DomainNameElement domainNameElement = 191 (DomainNameElement) anqpElementMap.get(ANQPElementType.ANQPDomName); 192 193 if (domainNameElement != null) { 194 for (String domain : domainNameElement.getDomains()) { 195 List<String> anLabels = Utils.splitDomain(domain); 196 DomainMatcher.Match match = mDomainMatcher.isSubDomain(anLabels); 197 if (match != DomainMatcher.Match.None) { 198 return PasspointMatch.HomeProvider; 199 } 200 201 if (imsiMatch(imsis, anLabels) != null) { 202 return PasspointMatch.HomeProvider; 203 } 204 } 205 } 206 207 return roamingMatch ? PasspointMatch.RoamingProvider : PasspointMatch.None; 208 } 209 210 private String imsiMatch(List<String> imsis, ThreeGPPNetworkElement plmnElement) { 211 if (imsis == null || plmnElement == null || plmnElement.getPlmns().isEmpty()) { 212 return null; 213 } 214 for (CellularNetwork network : plmnElement.getPlmns()) { 215 for (String mccMnc : network) { 216 String imsi = imsiMatch(imsis, mccMnc); 217 if (imsi != null) { 218 return imsi; 219 } 220 } 221 } 222 return null; 223 } 224 225 private String imsiMatch(List<String> imsis, List<String> fqdn) { 226 if (imsis == null) { 227 return null; 228 } 229 String mccMnc = Utils.getMccMnc(fqdn); 230 return mccMnc != null ? imsiMatch(imsis, mccMnc) : null; 231 } 232 233 private String imsiMatch(List<String> imsis, String mccMnc) { 234 if (mCredential.getImsi().matchesMccMnc(mccMnc)) { 235 for (String imsi : imsis) { 236 if (imsi.startsWith(mccMnc)) { 237 return imsi; 238 } 239 } 240 } 241 return null; 242 } 243 244 public String getFQDN() { return mFQDN; } 245 public String getFriendlyName() { return mFriendlyName; } 246 public HashSet<Long> getRoamingConsortiums() { return mRoamingConsortiums; } 247 public Credential getCredential() { return mCredential; } 248 249 public Map<String, Long> getSSIDs() { 250 return mSSIDs; 251 } 252 253 public Collection<String> getOtherHomePartners() { 254 return mOtherHomePartners; 255 } 256 257 public Set<Long> getMatchAnyOIs() { 258 return mMatchAnyOIs; 259 } 260 261 public List<Long> getMatchAllOIs() { 262 return mMatchAllOIs; 263 } 264 265 public String getIconURL() { 266 return mIconURL; 267 } 268 269 public boolean deepEquals(HomeSP other) { 270 return mFQDN.equals(other.mFQDN) && 271 mSSIDs.equals(other.mSSIDs) && 272 mOtherHomePartners.equals(other.mOtherHomePartners) && 273 mRoamingConsortiums.equals(other.mRoamingConsortiums) && 274 mMatchAnyOIs.equals(other.mMatchAnyOIs) && 275 mMatchAllOIs.equals(other.mMatchAllOIs) && 276 mFriendlyName.equals(other.mFriendlyName) && 277 Utils.compare(mIconURL, other.mIconURL) == 0 && 278 mCredential.equals(other.mCredential); 279 } 280 281 @Override 282 public boolean equals(Object thatObject) { 283 if (this == thatObject) { 284 return true; 285 } else if (thatObject == null || getClass() != thatObject.getClass()) { 286 return false; 287 } 288 289 HomeSP that = (HomeSP) thatObject; 290 return mFQDN.equals(that.mFQDN); 291 } 292 293 @Override 294 public int hashCode() { 295 return mFQDN.hashCode(); 296 } 297 298 @Override 299 public String toString() { 300 return "HomeSP{" + 301 "mSSIDs=" + mSSIDs + 302 ", mFQDN='" + mFQDN + '\'' + 303 ", mDomainMatcher=" + mDomainMatcher + 304 ", mRoamingConsortiums={" + Utils.roamingConsortiumsToString(mRoamingConsortiums) + 305 '}' + 306 ", mMatchAnyOIs={" + Utils.roamingConsortiumsToString(mMatchAnyOIs) + '}' + 307 ", mMatchAllOIs={" + Utils.roamingConsortiumsToString(mMatchAllOIs) + '}' + 308 ", mCredential=" + mCredential + 309 ", mFriendlyName='" + mFriendlyName + '\'' + 310 ", mIconURL='" + mIconURL + '\'' + 311 '}'; 312 } 313} 314