1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi.hotspot2;
18
19import com.android.server.wifi.IMSIParameter;
20import com.android.server.wifi.hotspot2.anqp.CellularNetwork;
21import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
22import com.android.server.wifi.hotspot2.anqp.NAIRealmData;
23import com.android.server.wifi.hotspot2.anqp.NAIRealmElement;
24import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement;
25import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
26import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
27import com.android.server.wifi.hotspot2.anqp.eap.EAPMethod;
28
29import java.util.List;
30import java.util.Map;
31import java.util.Set;
32
33/**
34 * Utility class for providing matching functions against ANQP elements.
35 */
36public class ANQPMatcher {
37    /**
38     * Match the domain names in the ANQP element against the provider's FQDN and SIM credential.
39     * The Domain Name ANQP element might contain domains for 3GPP network (e.g.
40     * wlan.mnc*.mcc*.3gppnetwork.org), so we should match that against the provider's SIM
41     * credential if one is provided.
42     *
43     * @param element The Domain Name ANQP element
44     * @param fqdn The FQDN to compare against
45     * @param imsiParam The IMSI parameter of the provider
46     * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
47     *                    IMSI parameter
48     * @return true if a match is found
49     */
50    public static boolean matchDomainName(DomainNameElement element, String fqdn,
51            IMSIParameter imsiParam, List<String> simImsiList) {
52        if (element == null) {
53            return false;
54        }
55
56        for (String domain : element.getDomains()) {
57            if (DomainMatcher.arg2SubdomainOfArg1(fqdn, domain)) {
58                return true;
59            }
60
61            // Try to retrieve the MCC-MNC string from the domain (for 3GPP network domain) and
62            // match against the provider's SIM credential.
63            if (matchMccMnc(Utils.getMccMnc(Utils.splitDomain(domain)), imsiParam, simImsiList)) {
64                return true;
65            }
66        }
67        return false;
68    }
69
70    /**
71     * Match the roaming consortium OIs in the ANQP element against the roaming consortium OIs
72     * of a provider.
73     *
74     * @param element The Roaming Consortium ANQP element
75     * @param providerOIs The roaming consortium OIs of the provider
76     * @return true if a match is found
77     */
78    public static boolean matchRoamingConsortium(RoamingConsortiumElement element,
79            long[] providerOIs) {
80        if (element == null) {
81            return false;
82        }
83        if (providerOIs == null) {
84            return false;
85        }
86        List<Long> rcOIs = element.getOIs();
87        for (long oi : providerOIs) {
88            if (rcOIs.contains(oi)) {
89                return true;
90            }
91        }
92        return false;
93    }
94
95    /**
96     * Match the NAI realm in the ANQP element against the realm and authentication method of
97     * a provider.
98     *
99     * @param element The NAI Realm ANQP element
100     * @param realm The realm of the provider's credential
101     * @param eapMethodID The EAP Method ID of the provider's credential
102     * @param authParam The authentication parameter of the provider's credential
103     * @return an integer indicating the match status
104     */
105    public static int matchNAIRealm(NAIRealmElement element, String realm, int eapMethodID,
106            AuthParam authParam) {
107        if (element == null || element.getRealmDataList().isEmpty()) {
108            return AuthMatch.INDETERMINATE;
109        }
110
111        int bestMatch = AuthMatch.NONE;
112        for (NAIRealmData realmData : element.getRealmDataList()) {
113            int match = matchNAIRealmData(realmData, realm, eapMethodID, authParam);
114            if (match > bestMatch) {
115                bestMatch = match;
116                if (bestMatch == AuthMatch.EXACT) {
117                    break;
118                }
119            }
120        }
121        return bestMatch;
122    }
123
124    /**
125     * Match the 3GPP Network in the ANQP element against the SIM credential of a provider.
126     *
127     * @param element 3GPP Network ANQP element
128     * @param imsiParam The IMSI parameter of the provider's SIM credential
129     * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
130     *                    IMSI parameter
131     * @return true if a matched is found
132     */
133    public static  boolean matchThreeGPPNetwork(ThreeGPPNetworkElement element,
134            IMSIParameter imsiParam, List<String> simImsiList) {
135        if (element == null) {
136            return false;
137        }
138        for (CellularNetwork network : element.getNetworks()) {
139            if (matchCellularNetwork(network, imsiParam, simImsiList)) {
140                return true;
141            }
142        }
143        return false;
144    }
145
146    /**
147     * Match the given NAI Realm data against the realm and authentication method of a provider.
148     *
149     * @param realmData The NAI Realm data
150     * @param realm The realm of the provider's credential
151     * @param eapMethodID The EAP Method ID of the provider's credential
152     * @param authParam The authentication parameter of the provider's credential
153     * @return an integer indicating the match status
154     */
155    private static int matchNAIRealmData(NAIRealmData realmData, String realm, int eapMethodID,
156            AuthParam authParam) {
157        // Check for realm domain name match.
158        int realmMatch = AuthMatch.NONE;
159        for (String realmStr : realmData.getRealms()) {
160            if (DomainMatcher.arg2SubdomainOfArg1(realm, realmStr)) {
161                realmMatch = AuthMatch.REALM;
162                break;
163            }
164        }
165
166        if (realmMatch == AuthMatch.NONE || realmData.getEAPMethods().isEmpty()) {
167            return realmMatch;
168        }
169
170        // Check for EAP method match.
171        int eapMethodMatch = AuthMatch.NONE;
172        for (EAPMethod eapMethod : realmData.getEAPMethods()) {
173            eapMethodMatch = matchEAPMethod(eapMethod, eapMethodID, authParam);
174            if (eapMethodMatch != AuthMatch.NONE) {
175                break;
176            }
177        }
178
179        if (eapMethodMatch == AuthMatch.NONE) {
180            return AuthMatch.NONE;
181        }
182        return realmMatch | eapMethodMatch;
183    }
184
185    /**
186     * Match the given EAPMethod against the authentication method of a provider.
187     *
188     * @param method The EAP Method
189     * @param eapMethodID The EAP Method ID of the provider's credential
190     * @param authParam The authentication parameter of the provider's credential
191     * @return an integer indicating the match status
192     */
193    private static int matchEAPMethod(EAPMethod method, int eapMethodID, AuthParam authParam) {
194        if (method.getEAPMethodID() != eapMethodID) {
195            return AuthMatch.NONE;
196        }
197        // Check for authentication parameter match.
198        if (authParam != null) {
199            Map<Integer, Set<AuthParam>> authParams = method.getAuthParams();
200            Set<AuthParam> paramSet = authParams.get(authParam.getAuthTypeID());
201            if (paramSet == null || !paramSet.contains(authParam)) {
202                return AuthMatch.NONE;
203            }
204            return AuthMatch.METHOD_PARAM;
205        }
206        return AuthMatch.METHOD;
207    }
208
209    /**
210     * Match a cellular network information in the 3GPP Network ANQP element against the SIM
211     * credential of a provider.
212     *
213     * @param network The cellular network that contained list of PLMNs
214     * @param imsiParam IMSI parameter of the provider
215     * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
216     *                    IMSI parameter
217     * @return true if a match is found
218     */
219    private static boolean matchCellularNetwork(CellularNetwork network, IMSIParameter imsiParam,
220            List<String> simImsiList) {
221        for (String plmn : network.getPlmns()) {
222            if (matchMccMnc(plmn, imsiParam, simImsiList)) {
223                return true;
224            }
225        }
226        return false;
227    }
228
229    /**
230     * Match a MCC-MNC against the SIM credential of a provider.
231     *
232     * @param mccMnc The string containing MCC-MNC
233     * @param imsiParam The IMSI parameter of the provider
234     * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
235     *                    IMSI parameter
236     * @return true if a match is found
237     */
238    private static boolean matchMccMnc(String mccMnc, IMSIParameter imsiParam,
239            List<String> simImsiList) {
240        if (imsiParam == null || simImsiList == null) {
241            return false;
242        }
243        // Match against the IMSI parameter in the provider.
244        if (!imsiParam.matchesMccMnc(mccMnc)) {
245            return false;
246        }
247        // Additional check for verifying the match with IMSIs from the SIM cards, since the IMSI
248        // parameter might not contain the full 6-digit MCC MNC (e.g. IMSI parameter is an IMSI
249        // prefix that contained less than 6-digit of numbers "12345*").
250        for (String imsi : simImsiList) {
251            if (imsi.startsWith(mccMnc)) {
252                return true;
253            }
254        }
255        return false;
256    }
257}
258