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