1/*
2 * Copyright (C) 2017 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;
18
19import static org.junit.Assert.*;
20import static org.mockito.Mockito.*;
21
22import android.net.wifi.WifiConfiguration;
23import android.net.wifi.WifiEnterpriseConfig;
24import android.test.suitebuilder.annotation.SmallTest;
25import android.util.Xml;
26
27import com.android.internal.util.FastXmlSerializer;
28import com.android.server.wifi.util.XmlUtilTest;
29
30import org.junit.Before;
31import org.junit.Test;
32import org.xmlpull.v1.XmlPullParser;
33import org.xmlpull.v1.XmlPullParserException;
34import org.xmlpull.v1.XmlSerializer;
35
36import java.io.ByteArrayInputStream;
37import java.io.ByteArrayOutputStream;
38import java.nio.charset.StandardCharsets;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.List;
42
43/**
44 * Unit tests for {@link com.android.server.wifi.NetworksListStoreData}.
45 */
46@SmallTest
47public class NetworkListStoreDataTest {
48
49    private static final String TEST_SSID = "WifiConfigStoreDataSSID_";
50    private static final String TEST_CONNECT_CHOICE = "XmlUtilConnectChoice";
51    private static final long TEST_CONNECT_CHOICE_TIMESTAMP = 0x4566;
52    private static final String SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT =
53            "<Network>\n"
54                    + "<WifiConfiguration>\n"
55                    + "<string name=\"ConfigKey\">%s</string>\n"
56                    + "<string name=\"SSID\">%s</string>\n"
57                    + "<null name=\"BSSID\" />\n"
58                    + "<null name=\"PreSharedKey\" />\n"
59                    + "<null name=\"WEPKeys\" />\n"
60                    + "<int name=\"WEPTxKeyIndex\" value=\"0\" />\n"
61                    + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
62                    + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
63                    + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">01</byte-array>\n"
64                    + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
65                    + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
66                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
67                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
68                    + "<boolean name=\"Shared\" value=\"%s\" />\n"
69                    + "<int name=\"Status\" value=\"2\" />\n"
70                    + "<null name=\"FQDN\" />\n"
71                    + "<null name=\"ProviderFriendlyName\" />\n"
72                    + "<null name=\"LinkedNetworksList\" />\n"
73                    + "<null name=\"DefaultGwMacAddress\" />\n"
74                    + "<boolean name=\"ValidatedInternetAccess\" value=\"false\" />\n"
75                    + "<boolean name=\"NoInternetAccessExpected\" value=\"false\" />\n"
76                    + "<int name=\"UserApproved\" value=\"0\" />\n"
77                    + "<boolean name=\"MeteredHint\" value=\"false\" />\n"
78                    + "<boolean name=\"UseExternalScores\" value=\"false\" />\n"
79                    + "<int name=\"NumAssociation\" value=\"0\" />\n"
80                    + "<int name=\"CreatorUid\" value=\"%d\" />\n"
81                    + "<null name=\"CreatorName\" />\n"
82                    + "<null name=\"CreationTime\" />\n"
83                    + "<int name=\"LastUpdateUid\" value=\"-1\" />\n"
84                    + "<null name=\"LastUpdateName\" />\n"
85                    + "<int name=\"LastConnectUid\" value=\"0\" />\n"
86                    + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
87                    + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
88                    + "</WifiConfiguration>\n"
89                    + "<NetworkStatus>\n"
90                    + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
91                    + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
92                    + "<null name=\"ConnectChoice\" />\n"
93                    + "<long name=\"ConnectChoiceTimeStamp\" value=\"-1\" />\n"
94                    + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
95                    + "</NetworkStatus>\n"
96                    + "<IpConfiguration>\n"
97                    + "<string name=\"IpAssignment\">DHCP</string>\n"
98                    + "<string name=\"ProxySettings\">NONE</string>\n"
99                    + "</IpConfiguration>\n"
100                    + "</Network>\n";
101
102    private static final String SINGLE_EAP_NETWORK_DATA_XML_STRING_FORMAT =
103            "<Network>\n"
104                    + "<WifiConfiguration>\n"
105                    + "<string name=\"ConfigKey\">%s</string>\n"
106                    + "<string name=\"SSID\">%s</string>\n"
107                    + "<null name=\"BSSID\" />\n"
108                    + "<null name=\"PreSharedKey\" />\n"
109                    + "<null name=\"WEPKeys\" />\n"
110                    + "<int name=\"WEPTxKeyIndex\" value=\"0\" />\n"
111                    + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
112                    + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
113                    + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">0c</byte-array>\n"
114                    + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
115                    + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
116                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
117                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
118                    + "<boolean name=\"Shared\" value=\"%s\" />\n"
119                    + "<int name=\"Status\" value=\"2\" />\n"
120                    + "<null name=\"FQDN\" />\n"
121                    + "<null name=\"ProviderFriendlyName\" />\n"
122                    + "<null name=\"LinkedNetworksList\" />\n"
123                    + "<null name=\"DefaultGwMacAddress\" />\n"
124                    + "<boolean name=\"ValidatedInternetAccess\" value=\"false\" />\n"
125                    + "<boolean name=\"NoInternetAccessExpected\" value=\"false\" />\n"
126                    + "<int name=\"UserApproved\" value=\"0\" />\n"
127                    + "<boolean name=\"MeteredHint\" value=\"false\" />\n"
128                    + "<boolean name=\"UseExternalScores\" value=\"false\" />\n"
129                    + "<int name=\"NumAssociation\" value=\"0\" />\n"
130                    + "<int name=\"CreatorUid\" value=\"%d\" />\n"
131                    + "<null name=\"CreatorName\" />\n"
132                    + "<null name=\"CreationTime\" />\n"
133                    + "<int name=\"LastUpdateUid\" value=\"-1\" />\n"
134                    + "<null name=\"LastUpdateName\" />\n"
135                    + "<int name=\"LastConnectUid\" value=\"0\" />\n"
136                    + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
137                    + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
138                    + "</WifiConfiguration>\n"
139                    + "<NetworkStatus>\n"
140                    + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
141                    + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
142                    + "<null name=\"ConnectChoice\" />\n"
143                    + "<long name=\"ConnectChoiceTimeStamp\" value=\"-1\" />\n"
144                    + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
145                    + "</NetworkStatus>\n"
146                    + "<IpConfiguration>\n"
147                    + "<string name=\"IpAssignment\">DHCP</string>\n"
148                    + "<string name=\"ProxySettings\">NONE</string>\n"
149                    + "</IpConfiguration>\n"
150                    + "<WifiEnterpriseConfiguration>\n"
151                    + "<string name=\"Identity\"></string>\n"
152                    + "<string name=\"AnonIdentity\"></string>\n"
153                    + "<string name=\"Password\"></string>\n"
154                    + "<string name=\"ClientCert\"></string>\n"
155                    + "<string name=\"CaCert\"></string>\n"
156                    + "<string name=\"SubjectMatch\"></string>\n"
157                    + "<string name=\"Engine\"></string>\n"
158                    + "<string name=\"EngineId\"></string>\n"
159                    + "<string name=\"PrivateKeyId\"></string>\n"
160                    + "<string name=\"AltSubjectMatch\"></string>\n"
161                    + "<string name=\"DomSuffixMatch\"></string>\n"
162                    + "<string name=\"CaPath\"></string>\n"
163                    + "<int name=\"EapMethod\" value=\"2\" />\n"
164                    + "<int name=\"Phase2Method\" value=\"0\" />\n"
165                    + "<string name=\"PLMN\"></string>\n"
166                    + "<string name=\"Realm\"></string>\n"
167                    + "</WifiEnterpriseConfiguration>\n"
168                    + "</Network>\n";
169
170    private NetworkListStoreData mNetworkListStoreData;
171
172    @Before
173    public void setUp() throws Exception {
174        mNetworkListStoreData = new NetworkListStoreData();
175    }
176
177    /**
178     * Helper function for serializing configuration data to a XML block.
179     *
180     * @param shared Flag indicating serializing shared or user configurations
181     * @return byte[] of the XML data
182     * @throws Exception
183     */
184    private byte[] serializeData(boolean shared) throws Exception {
185        final XmlSerializer out = new FastXmlSerializer();
186        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
187        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
188        mNetworkListStoreData.serializeData(out, shared);
189        out.flush();
190        return outputStream.toByteArray();
191    }
192
193    /**
194     * Helper function for parsing configuration data from a XML block.
195     *
196     * @param data XML data to parse from
197     * @param shared Flag indicating parsing of shared or user configurations
198     * @return List of WifiConfiguration parsed
199     * @throws Exception
200     */
201    private List<WifiConfiguration> deserializeData(byte[] data, boolean shared) throws Exception {
202        final XmlPullParser in = Xml.newPullParser();
203        final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
204        in.setInput(inputStream, StandardCharsets.UTF_8.name());
205        mNetworkListStoreData.deserializeData(in, in.getDepth(), shared);
206        if (shared) {
207            return mNetworkListStoreData.getSharedConfigurations();
208        } else {
209            return mNetworkListStoreData.getUserConfigurations();
210        }
211    }
212
213    /**
214     * Helper function for generating a network list for testing purpose.  The network list
215     * will contained an open and an EAP network.
216     *
217     * @param shared Flag indicating shared network
218     * @return List of WifiConfiguration
219     */
220    private List<WifiConfiguration> getTestNetworksConfig(boolean shared) {
221        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
222        openNetwork.shared = shared;
223        openNetwork.setIpConfiguration(
224                WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
225        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
226        eapNetwork.shared = shared;
227        eapNetwork.setIpConfiguration(
228                WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
229        List<WifiConfiguration> networkList = new ArrayList<>();
230        networkList.add(openNetwork);
231        networkList.add(eapNetwork);
232        return networkList;
233    }
234
235    /**
236     * Helper function for generating XML block containing two networks, an open and an EAP
237     * network.
238     *
239     * @param openNetwork The WifiConfiguration for an open network
240     * @param eapNetwork The WifiConfiguration for an EAP network
241     * @return byte[] of the XML data
242     */
243    private byte[] getTestNetworksXmlBytes(WifiConfiguration openNetwork,
244            WifiConfiguration eapNetwork) {
245        String openNetworkXml = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
246                openNetwork.configKey().replaceAll("\"", "&quot;"),
247                openNetwork.SSID.replaceAll("\"", "&quot;"),
248                openNetwork.shared, openNetwork.creatorUid);
249        String eapNetworkXml = String.format(SINGLE_EAP_NETWORK_DATA_XML_STRING_FORMAT,
250                eapNetwork.configKey().replaceAll("\"", "&quot;"),
251                eapNetwork.SSID.replaceAll("\"", "&quot;"),
252                eapNetwork.shared, eapNetwork.creatorUid);
253        return (openNetworkXml + eapNetworkXml).getBytes(StandardCharsets.UTF_8);
254    }
255
256    /**
257     * Verify that serializing the store data without any configuration doesn't cause any crash
258     * and no data should be serialized.
259     *
260     * @throws Exception
261     */
262    @Test
263    public void serializeEmptyConfigs() throws Exception {
264        assertEquals(0, serializeData(true /* shared */).length);
265        assertEquals(0, serializeData(false /* shared */).length);
266    }
267
268    /**
269     * Verify that parsing an empty data doesn't cause any crash and no configuration should
270     * be parsed.
271     *
272     * @throws Exception
273     */
274    @Test
275    public void deserializeEmptyData() throws Exception {
276        assertTrue(deserializeData(new byte[0], true /* shared */).isEmpty());
277        assertTrue(deserializeData(new byte[0], false /* shared */).isEmpty());
278    }
279
280    /**
281     * Verify that NetworkListStoreData does support share data.
282     *
283     * @throws Exception
284     */
285    @Test
286    public void supportShareData() throws Exception {
287        assertTrue(mNetworkListStoreData.supportShareData());
288    }
289
290    /**
291     * Verify that the shared configurations (containing an open and an EAP network) are serialized
292     * correctly, matching the expected XML string.
293     *
294     * @throws Exception
295     */
296    @Test
297    public void serializeSharedConfigurations() throws Exception {
298        List<WifiConfiguration> networkList = getTestNetworksConfig(true /* shared */);
299        mNetworkListStoreData.setSharedConfigurations(networkList);
300        byte[] expectedData = getTestNetworksXmlBytes(networkList.get(0), networkList.get(1));
301        assertTrue(Arrays.equals(expectedData, serializeData(true /* shared */)));
302    }
303
304    /**
305     * Verify that the shared configurations are parsed correctly from a XML string containing
306     * test networks (an open and an EAP network).
307     * @throws Exception
308     */
309    @Test
310    public void deserializeSharedConfigurations() throws Exception {
311        List<WifiConfiguration> networkList = getTestNetworksConfig(true /* shared */);
312        byte[] xmlData = getTestNetworksXmlBytes(networkList.get(0), networkList.get(1));
313        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore(
314                networkList, deserializeData(xmlData, true /* shared */));
315    }
316
317    /**
318     * Verify that the user configurations (containing an open and an EAP network) are serialized
319     * correctly, matching the expected XML string.
320     *
321     * @throws Exception
322     */
323    @Test
324    public void serializeUserConfigurations() throws Exception {
325        List<WifiConfiguration> networkList = getTestNetworksConfig(false /* shared */);
326        mNetworkListStoreData.setUserConfigurations(networkList);
327        byte[] expectedData = getTestNetworksXmlBytes(networkList.get(0), networkList.get(1));
328        assertTrue(Arrays.equals(expectedData, serializeData(false /* shared */)));
329    }
330
331    /**
332     * Verify that the user configurations are parsed correctly from a XML string containing
333     * test networks (an open and an EAP network).
334     * @throws Exception
335     */
336    @Test
337    public void deserializeUserConfigurations() throws Exception {
338        List<WifiConfiguration> networkList = getTestNetworksConfig(false /* shared */);
339        byte[] xmlData = getTestNetworksXmlBytes(networkList.get(0), networkList.get(1));
340        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore(
341                networkList, deserializeData(xmlData, false /* shared */));
342    }
343
344    /**
345     * Verify that a XmlPullParserException will be thrown when parsing a <Network> block
346     * containing an unknown tag.
347     *
348     * @throws Exception
349     */
350    @Test(expected = XmlPullParserException.class)
351    public void parseNetworkWithUnknownTag() throws Exception {
352        String configFormat =
353                "<Network>\n"
354                        + "<WifiConfiguration>\n"
355                        + "<string name=\"ConfigKey\">%s</string>\n"
356                        + "<string name=\"SSID\">%s</string>\n"
357                        + "<null name=\"BSSID\" />\n"
358                        + "<null name=\"PreSharedKey\" />\n"
359                        + "<null name=\"WEPKeys\" />\n"
360                        + "<int name=\"WEPTxKeyIndex\" value=\"0\" />\n"
361                        + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
362                        + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
363                        + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">01</byte-array>\n"
364                        + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
365                        + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
366                        + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
367                        + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
368                        + "<boolean name=\"Shared\" value=\"%s\" />\n"
369                        + "<null name=\"FQDN\" />\n"
370                        + "<null name=\"ProviderFriendlyName\" />\n"
371                        + "<null name=\"LinkedNetworksList\" />\n"
372                        + "<null name=\"DefaultGwMacAddress\" />\n"
373                        + "<boolean name=\"ValidatedInternetAccess\" value=\"false\" />\n"
374                        + "<boolean name=\"NoInternetAccessExpected\" value=\"false\" />\n"
375                        + "<int name=\"UserApproved\" value=\"0\" />\n"
376                        + "<boolean name=\"MeteredHint\" value=\"false\" />\n"
377                        + "<boolean name=\"UseExternalScores\" value=\"false\" />\n"
378                        + "<int name=\"NumAssociation\" value=\"0\" />\n"
379                        + "<int name=\"CreatorUid\" value=\"%d\" />\n"
380                        + "<null name=\"CreatorName\" />\n"
381                        + "<null name=\"CreationTime\" />\n"
382                        + "<int name=\"LastUpdateUid\" value=\"-1\" />\n"
383                        + "<null name=\"LastUpdateName\" />\n"
384                        + "<int name=\"LastConnectUid\" value=\"0\" />\n"
385                        + "</WifiConfiguration>\n"
386                        + "<NetworkStatus>\n"
387                        + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
388                        + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
389                        + "<null name=\"ConnectChoice\" />\n"
390                        + "<long name=\"ConnectChoiceTimeStamp\" value=\"-1\" />\n"
391                        + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
392                        + "</NetworkStatus>\n"
393                        + "<IpConfiguration>\n"
394                        + "<string name=\"IpAssignment\">DHCP</string>\n"
395                        + "<string name=\"ProxySettings\">NONE</string>\n"
396                        + "</IpConfiguration>\n"
397                        + "<Unknown>"       // Unknown tag.
398                        + "<int name=\"test\" value=\"0\" />\n"
399                        + "</Unknown>"
400                        + "</Network>\n";
401        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
402        byte[] xmlData = String.format(configFormat,
403                openNetwork.configKey().replaceAll("\"", "&quot;"),
404                openNetwork.SSID.replaceAll("\"", "&quot;"),
405                openNetwork.shared, openNetwork.creatorUid).getBytes(StandardCharsets.UTF_8);
406        deserializeData(xmlData, true);
407    }
408
409    /**
410     * Verify that a XmlPullParseException will be thrown when parsing a network configuration
411     * containing a mismatched config key.
412     *
413     * @throws Exception
414     */
415    @Test(expected = XmlPullParserException.class)
416    public void parseNetworkWithMismatchConfigKey() throws Exception {
417        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
418        byte[] xmlData = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
419                "InvalidConfigKey",
420                openNetwork.SSID.replaceAll("\"", "&quot;"),
421                openNetwork.shared, openNetwork.creatorUid).getBytes(StandardCharsets.UTF_8);
422        deserializeData(xmlData, true);
423    }
424
425    /**
426     * Tests that an invalid data in one of the WifiConfiguration object parsing would be skipped
427     * gracefully. The other networks in the XML should still be parsed out correctly.
428     */
429    @Test
430    public void parseNetworkListWithOneNetworkIllegalArgException() throws Exception {
431        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
432        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
433        String xmlString = new String(getTestNetworksXmlBytes(openNetwork, eapNetwork));
434        // Manipulate the XML data to set the EAP method to None, this should raise an Illegal
435        // argument exception in WifiEnterpriseConfig.setEapMethod().
436        xmlString = xmlString.replaceAll(
437                String.format(XmlUtilTest.XML_STRING_EAP_METHOD_REPLACE_FORMAT,
438                        eapNetwork.enterpriseConfig.getEapMethod()),
439                String.format(XmlUtilTest.XML_STRING_EAP_METHOD_REPLACE_FORMAT,
440                        WifiEnterpriseConfig.Eap.NONE));
441        List<WifiConfiguration> retrievedNetworkList =
442                deserializeData(xmlString.getBytes(StandardCharsets.UTF_8), true /* shared */);
443        // Retrieved network should not contain the eap network.
444        assertEquals(1, retrievedNetworkList.size());
445        for (WifiConfiguration network : retrievedNetworkList) {
446            assertNotEquals(eapNetwork.SSID, network.SSID);
447        }
448    }
449}
450