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