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