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 
17 package com.android.server.wifi;
18 
19 import static org.junit.Assert.*;
20 import static org.mockito.Mockito.*;
21 
22 import android.app.test.TestAlarmManager;
23 import android.content.Context;
24 import android.content.pm.PackageManager;
25 import android.net.MacAddress;
26 import android.net.wifi.WifiConfiguration;
27 import android.os.test.TestLooper;
28 
29 import androidx.test.filters.SmallTest;
30 
31 import com.android.internal.util.ArrayUtils;
32 import com.android.server.wifi.WifiConfigStore.StoreData;
33 import com.android.server.wifi.WifiConfigStore.StoreFile;
34 import com.android.server.wifi.util.EncryptedData;
35 import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
36 import com.android.server.wifi.util.XmlUtil;
37 
38 import libcore.util.HexEncoding;
39 
40 import org.junit.After;
41 import org.junit.Before;
42 import org.junit.Test;
43 import org.mockito.Mock;
44 import org.mockito.MockitoAnnotations;
45 import org.xmlpull.v1.XmlPullParser;
46 import org.xmlpull.v1.XmlPullParserException;
47 import org.xmlpull.v1.XmlSerializer;
48 
49 import java.io.File;
50 import java.io.IOException;
51 import java.nio.charset.StandardCharsets;
52 import java.util.ArrayList;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Random;
57 
58 /**
59  * Unit tests for {@link com.android.server.wifi.WifiConfigStore}.
60  */
61 @SmallTest
62 public class WifiConfigStoreTest {
63     private static final String TEST_USER_DATA = "UserData";
64     private static final String TEST_SHARE_DATA = "ShareData";
65     private static final String TEST_CREATOR_NAME = "CreatorName";
66     private static final MacAddress TEST_RANDOMIZED_MAC =
67             MacAddress.fromString("da:a1:19:c4:26:fa");
68 
69     private static final String TEST_DATA_XML_STRING_FORMAT =
70             "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
71                     + "<WifiConfigStoreData>\n"
72                     + "<int name=\"Version\" value=\"3\" />\n"
73                     + "<NetworkList>\n"
74                     + "<Network>\n"
75                     + "<WifiConfiguration>\n"
76                     + "<string name=\"ConfigKey\">%s</string>\n"
77                     + "<string name=\"SSID\">%s</string>\n"
78                     + "<null name=\"BSSID\" />\n"
79                     + "<null name=\"PreSharedKey\" />\n"
80                     + "<null name=\"WEPKeys\" />\n"
81                     + "<int name=\"WEPTxKeyIndex\" value=\"0\" />\n"
82                     + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
83                     + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
84                     + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">01</byte-array>\n"
85                     + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
86                     + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
87                     + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
88                     + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
89                     + "<byte-array name=\"AllowedGroupMgmtCiphers\" num=\"0\"></byte-array>\n"
90                     + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
91                     + "<boolean name=\"Shared\" value=\"%s\" />\n"
92                     + "<int name=\"Status\" value=\"2\" />\n"
93                     + "<null name=\"FQDN\" />\n"
94                     + "<null name=\"ProviderFriendlyName\" />\n"
95                     + "<null name=\"LinkedNetworksList\" />\n"
96                     + "<null name=\"DefaultGwMacAddress\" />\n"
97                     + "<boolean name=\"ValidatedInternetAccess\" value=\"false\" />\n"
98                     + "<boolean name=\"NoInternetAccessExpected\" value=\"false\" />\n"
99                     + "<int name=\"UserApproved\" value=\"0\" />\n"
100                     + "<boolean name=\"MeteredHint\" value=\"false\" />\n"
101                     + "<int name=\"MeteredOverride\" value=\"0\" />\n"
102                     + "<boolean name=\"UseExternalScores\" value=\"false\" />\n"
103                     + "<int name=\"NumAssociation\" value=\"0\" />\n"
104                     + "<int name=\"CreatorUid\" value=\"%d\" />\n"
105                     + "<string name=\"CreatorName\">%s</string>\n"
106                     + "<null name=\"CreationTime\" />\n"
107                     + "<int name=\"LastUpdateUid\" value=\"-1\" />\n"
108                     + "<null name=\"LastUpdateName\" />\n"
109                     + "<int name=\"LastConnectUid\" value=\"0\" />\n"
110                     + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
111                     + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
112                     + "<string name=\"RandomizedMacAddress\">%s</string>\n"
113                     + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
114                     + "</WifiConfiguration>\n"
115                     + "<NetworkStatus>\n"
116                     + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
117                     + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
118                     + "<null name=\"ConnectChoice\" />\n"
119                     + "<long name=\"ConnectChoiceTimeStamp\" value=\"-1\" />\n"
120                     + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
121                     + "</NetworkStatus>\n"
122                     + "<IpConfiguration>\n"
123                     + "<string name=\"IpAssignment\">DHCP</string>\n"
124                     + "<string name=\"ProxySettings\">NONE</string>\n"
125                     + "</IpConfiguration>\n"
126                     + "</Network>\n"
127                     + "</NetworkList>\n"
128                     + "<DeletedEphemeralSSIDList>\n"
129                     + "<map name=\"SSIDList\">\n"
130                     + "<long name=\"%s\" value=\"0\" />\n"
131                     + "</map>\n"
132                     + "</DeletedEphemeralSSIDList>\n"
133                     + "</WifiConfigStoreData>\n";
134 
135     private static final String TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE =
136             "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
137                     + "<WifiConfigStoreData>\n"
138                     + "<int name=\"Version\" value=\"1\" />\n"
139                     + "<%s/>n"
140                     + "</WifiConfigStoreData>\n";
141     private static final String TEST_DATA_XML_STRING_FORMAT_V1_WITH_TWO_DATA_SOURCE =
142             "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
143                     + "<WifiConfigStoreData>\n"
144                     + "<int name=\"Version\" value=\"1\" />\n"
145                     + "<%s/>n"
146                     + "<%s/>n"
147                     + "</WifiConfigStoreData>\n";
148     private static final String TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE =
149             "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
150                     + "<WifiConfigStoreData>\n"
151                     + "<int name=\"Version\" value=\"2\" />\n"
152                     + "<Integrity>\n"
153                     + "<byte-array name=\"EncryptedData\" num=\"48\">%s</byte-array>\n"
154                     + "<byte-array name=\"IV\" num=\"12\">%s</byte-array>\n"
155                     + "</Integrity>\n"
156                     + "<%s />\n"
157                     + "</WifiConfigStoreData>\n";
158     // Test mocks
159     @Mock private Context mContext;
160     @Mock private PackageManager mPackageManager;
161     private TestAlarmManager mAlarmManager;
162     private TestLooper mLooper;
163     @Mock private Clock mClock;
164     @Mock private WifiMetrics mWifiMetrics;
165     @Mock private WifiConfigStoreEncryptionUtil mEncryptionUtil;
166     private MockStoreFile mSharedStore;
167     private MockStoreFile mUserStore;
168     private MockStoreFile mUserNetworkSuggestionsStore;
169     private List<StoreFile> mUserStores = new ArrayList<StoreFile>();
170     private MockStoreData mSharedStoreData;
171     private MockStoreData mUserStoreData;
172 
173     /**
174      * Test instance of WifiConfigStore.
175      */
176     private WifiConfigStore mWifiConfigStore;
177 
178     /**
179      * Setup mocks before the test starts.
180      */
setupMocks()181     private void setupMocks() throws Exception {
182         MockitoAnnotations.initMocks(this);
183         mAlarmManager = new TestAlarmManager();
184         mLooper = new TestLooper();
185         when(mContext.getSystemService(Context.ALARM_SERVICE))
186                 .thenReturn(mAlarmManager.getAlarmManager());
187         when(mContext.getPackageManager()).thenReturn(mPackageManager);
188         when(mPackageManager.getNameForUid(anyInt())).thenReturn(TEST_CREATOR_NAME);
189         when(mEncryptionUtil.encrypt(any(byte[].class)))
190                 .thenReturn(new EncryptedData(new byte[0], new byte[0]));
191         when(mEncryptionUtil.decrypt(any(EncryptedData.class)))
192                 .thenReturn(new byte[0]);
193         mSharedStore = new MockStoreFile(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
194         mUserStore = new MockStoreFile(WifiConfigStore.STORE_FILE_USER_GENERAL);
195         mUserNetworkSuggestionsStore =
196                 new MockStoreFile(WifiConfigStore.STORE_FILE_USER_NETWORK_SUGGESTIONS);
197         mUserStores.add(mUserStore);
198         mUserStores.add(mUserNetworkSuggestionsStore);
199 
200         mSharedStoreData = new MockStoreData(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
201         mUserStoreData = new MockStoreData(WifiConfigStore.STORE_FILE_USER_GENERAL);
202     }
203 
204     /**
205      * Setup the test environment.
206      */
207     @Before
setUp()208     public void setUp() throws Exception {
209         setupMocks();
210 
211         mWifiConfigStore = new WifiConfigStore(mContext, mLooper.getLooper(), mClock, mWifiMetrics,
212                 mSharedStore);
213         // Enable verbose logging before tests.
214         mWifiConfigStore.enableVerboseLogging(true);
215     }
216 
217     /**
218      * Called after each test
219      */
220     @After
cleanup()221     public void cleanup() {
222         validateMockitoUsage();
223     }
224 
225     /**
226      * Verify that no write occurs if there is {@link StoreData} registered for any
227      * {@link StoreFile}.
228      *
229      * @throws Exception
230      */
231     @Test
testWriteWithNoStoreData()232     public void testWriteWithNoStoreData() throws Exception {
233         // Perform force write to both share and user store file.
234         mWifiConfigStore.setUserStores(mUserStores);
235         mWifiConfigStore.write(true);
236 
237         assertFalse(mSharedStore.isStoreWritten());
238         assertFalse(mUserStore.isStoreWritten());
239         assertFalse(mUserNetworkSuggestionsStore.isStoreWritten());
240 
241         verify(mWifiMetrics, never()).noteWifiConfigStoreWriteDuration(anyInt());
242     }
243 
244     /**
245      * Tests the write API with the force flag set to true.
246      * Expected behavior: This should trigger an immediate write to the store files and no alarms
247      * should be started.
248      */
249     @Test
testForceWrite()250     public void testForceWrite() throws Exception {
251         // Register data container.
252         mWifiConfigStore.registerStoreData(mSharedStoreData);
253         mWifiConfigStore.registerStoreData(mUserStoreData);
254 
255         mWifiConfigStore.switchUserStoresAndRead(mUserStores);
256         mWifiConfigStore.write(true);
257 
258         assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
259         assertTrue(mSharedStore.isStoreWritten());
260         assertTrue(mUserStore.isStoreWritten());
261         assertFalse(mUserNetworkSuggestionsStore.isStoreWritten());
262 
263         verify(mWifiMetrics).noteWifiConfigStoreWriteDuration(anyInt());
264     }
265 
266     /**
267      * Tests the write API with the force flag set to false.
268      * Expected behavior: This should set an alarm to write to the store files.
269      */
270     @Test
testBufferedWrite()271     public void testBufferedWrite() throws Exception {
272         // Register data container.
273         mWifiConfigStore.registerStoreData(mSharedStoreData);
274         mWifiConfigStore.registerStoreData(mUserStoreData);
275 
276         mWifiConfigStore.switchUserStoresAndRead(mUserStores);
277         mWifiConfigStore.write(false);
278 
279         assertTrue(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
280         assertFalse(mSharedStore.isStoreWritten());
281         assertFalse(mUserStore.isStoreWritten());
282         assertFalse(mUserNetworkSuggestionsStore.isStoreWritten());
283 
284         // Now send the alarm and ensure that the writes happen.
285         mAlarmManager.dispatch(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG);
286         mLooper.dispatchAll();
287         assertTrue(mSharedStore.isStoreWritten());
288         assertTrue(mUserStore.isStoreWritten());
289         assertFalse(mUserNetworkSuggestionsStore.isStoreWritten());
290 
291         verify(mWifiMetrics).noteWifiConfigStoreWriteDuration(anyInt());
292     }
293 
294     /**
295      * Tests the force write after a buffered write.
296      * Expected behaviour: The force write should override the previous buffered write and stop the
297      * buffer write alarms.
298      */
299     @Test
testForceWriteAfterBufferedWrite()300     public void testForceWriteAfterBufferedWrite() throws Exception {
301         // Register a test data container with bogus data.
302         mWifiConfigStore.registerStoreData(mSharedStoreData);
303         mWifiConfigStore.registerStoreData(mUserStoreData);
304 
305         mSharedStoreData.setData("abcds");
306         mUserStoreData.setData("asdfa");
307 
308         // Perform buffered write for both user and share store file.
309         mWifiConfigStore.switchUserStoresAndRead(mUserStores);
310         mWifiConfigStore.write(false);
311 
312         assertTrue(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
313         assertFalse(mSharedStore.isStoreWritten());
314         assertFalse(mUserStore.isStoreWritten());
315 
316         // Update the container with new set of data. The send a force write and ensure that the
317         // writes have been performed and alarms have been stopped and updated data are written.
318         mUserStoreData.setData(TEST_USER_DATA);
319         mSharedStoreData.setData(TEST_SHARE_DATA);
320         mWifiConfigStore.write(true);
321 
322         assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
323         assertTrue(mSharedStore.isStoreWritten());
324         assertTrue(mUserStore.isStoreWritten());
325 
326         // Verify correct data are loaded to the data container after a read.
327         mWifiConfigStore.read();
328         assertEquals(TEST_USER_DATA, mUserStoreData.getData());
329         assertEquals(TEST_SHARE_DATA, mSharedStoreData.getData());
330     }
331 
332     /**
333      * Tests the force write with no new data after a buffered write.
334      * Expected behaviour: The force write should flush the previous buffered write and stop the
335      * buffer write alarms.
336      */
337     @Test
testForceWriteWithNoNewDataAfterBufferedWrite()338     public void testForceWriteWithNoNewDataAfterBufferedWrite() throws Exception {
339         // Register a test data container with bogus data.
340         mWifiConfigStore.registerStoreData(mSharedStoreData);
341         mWifiConfigStore.registerStoreData(mUserStoreData);
342 
343         mSharedStoreData.setData("abcds");
344         mUserStoreData.setData("asdfa");
345 
346         // Perform buffered write for both user and share store file.
347         mWifiConfigStore.switchUserStoresAndRead(mUserStores);
348         mWifiConfigStore.write(false);
349 
350         assertTrue(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
351         assertFalse(mSharedStore.isStoreWritten());
352         assertFalse(mUserStore.isStoreWritten());
353 
354         // Containers have no new data.
355         mUserStoreData.setHasAnyNewData(false);
356         mSharedStoreData.setHasAnyNewData(false);
357         mWifiConfigStore.write(true);
358 
359         assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
360         assertTrue(mSharedStore.isStoreWritten());
361         assertTrue(mUserStore.isStoreWritten());
362 
363         // Verify correct data are loaded to the data container after a read.
364         mWifiConfigStore.read();
365         assertEquals("abcds", mSharedStoreData.getData());
366         assertEquals("asdfa", mUserStoreData.getData());
367     }
368 
369 
370     /**
371      * Tests the read API behaviour after a write to the store files.
372      * Expected behaviour: The read should return the same data that was last written.
373      */
374     @Test
testReadAfterWrite()375     public void testReadAfterWrite() throws Exception {
376         // Register data container.
377         mWifiConfigStore.registerStoreData(mSharedStoreData);
378         mWifiConfigStore.registerStoreData(mUserStoreData);
379 
380         // Read both share and user config store.
381         mWifiConfigStore.switchUserStoresAndRead(mUserStores);
382 
383         // Verify no data is read.
384         assertNull(mUserStoreData.getData());
385         assertNull(mSharedStoreData.getData());
386 
387         // Write share and user data.
388         mUserStoreData.setData(TEST_USER_DATA);
389         mSharedStoreData.setData(TEST_SHARE_DATA);
390         mWifiConfigStore.write(true);
391 
392         // Read and verify the data content in the data container.
393         mWifiConfigStore.read();
394         assertEquals(TEST_USER_DATA, mUserStoreData.getData());
395         assertEquals(TEST_SHARE_DATA, mSharedStoreData.getData());
396 
397         verify(mWifiMetrics, times(2)).noteWifiConfigStoreReadDuration(anyInt());
398         verify(mWifiMetrics).noteWifiConfigStoreWriteDuration(anyInt());
399     }
400 
401     /**
402      * Tests the read API behaviour when the shared store file is empty and the user store
403      * is not yet visible (user not yet unlocked).
404      * Expected behaviour: The read should return an empty store data instance when the file not
405      * found exception is raised.
406      */
407     @Test
testReadWithNoSharedStoreFileAndUserStoreNotVisible()408     public void testReadWithNoSharedStoreFileAndUserStoreNotVisible() throws Exception {
409         StoreData sharedStoreData = mock(StoreData.class);
410         when(sharedStoreData.getStoreFileId())
411                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
412         StoreData userStoreData = mock(StoreData.class);
413         when(userStoreData.getStoreFileId())
414                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
415 
416         // Reading the mock store without a write should simulate the file not found case because
417         // |readRawData| would return null.
418         mWifiConfigStore.registerStoreData(sharedStoreData);
419         mWifiConfigStore.registerStoreData(userStoreData);
420         assertFalse(mWifiConfigStore.areStoresPresent());
421         mWifiConfigStore.read();
422 
423         // Ensure that we got the call to deserialize empty shared data, but no user data.
424         verify(sharedStoreData).resetData();
425         verify(sharedStoreData).deserializeData(eq(null), anyInt(), anyInt(), any());
426         verify(userStoreData, never()).resetData();
427         verify(userStoreData, never()).deserializeData(any(), anyInt(), anyInt(), any());
428     }
429 
430     /**
431      * Tests the read API behaviour when there are no user/shared store files on the device.
432      * Expected behaviour: The read should return an empty store data instance when the file not
433      * found exception is raised.
434      */
435     @Test
testReadWithNoStoreFiles()436     public void testReadWithNoStoreFiles() throws Exception {
437         StoreData sharedStoreData = mock(StoreData.class);
438         when(sharedStoreData.getStoreFileId())
439                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
440         StoreData userStoreData = mock(StoreData.class);
441         when(userStoreData.getStoreFileId())
442                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
443 
444         // Reading the mock store without a write should simulate the file not found case because
445         // |readRawData| would return null.
446         mWifiConfigStore.registerStoreData(sharedStoreData);
447         mWifiConfigStore.registerStoreData(userStoreData);
448         // Read both share and user config store.
449         mWifiConfigStore.setUserStores(mUserStores);
450         assertFalse(mWifiConfigStore.areStoresPresent());
451         mWifiConfigStore.read();
452 
453         // Ensure that we got the call to deserialize empty shared & user data.
454         verify(userStoreData).resetData();
455         verify(userStoreData).deserializeData(eq(null), anyInt(), anyInt(), any());
456         verify(sharedStoreData).resetData();
457         verify(sharedStoreData).deserializeData(eq(null), anyInt(), anyInt(), any());
458     }
459 
460     /**
461      * Tests the read API behaviour after a write to the shared store file when the user
462      * store file is null.
463      * Expected behaviour: The read should return the same data that was last written.
464      */
465     @Test
testReadAfterWriteWithNoUserStore()466     public void testReadAfterWriteWithNoUserStore() throws Exception {
467         // Setup data container.
468         mWifiConfigStore.registerStoreData(mSharedStoreData);
469         mSharedStoreData.setData(TEST_SHARE_DATA);
470 
471         // Perform write for the share store file.
472         mWifiConfigStore.write(true);
473         mWifiConfigStore.read();
474         // Verify data content for both user and share data.
475         assertEquals(TEST_SHARE_DATA, mSharedStoreData.getData());
476     }
477 
478     /**
479      * Verifies that a read operation will reset the data in the data container, to avoid
480      * any stale data from previous read.
481      *
482      * @throws Exception
483      */
484     @Test
testReadWillResetStoreData()485     public void testReadWillResetStoreData() throws Exception {
486         // Register and setup store data.
487         mWifiConfigStore.registerStoreData(mSharedStoreData);
488         mWifiConfigStore.registerStoreData(mUserStoreData);
489 
490         // Perform force write with empty data content to both user and share store file.
491         mWifiConfigStore.switchUserStoresAndRead(mUserStores);
492         mWifiConfigStore.write(true);
493 
494         // Setup data container with some value.
495         mUserStoreData.setData(TEST_USER_DATA);
496         mSharedStoreData.setData(TEST_SHARE_DATA);
497 
498         // Perform read of both user and share store file and verify data in the data container
499         // is in sync (empty) with what is in the file.
500         mWifiConfigStore.read();
501         assertNull(mSharedStoreData.getData());
502         assertNull(mUserStoreData.getData());
503     }
504 
505     /**
506      * Verify that a store file contained WiFi configuration store data (network list and
507      * deleted ephemeral SSID list) using the predefined test XML data is read and parsed
508      * correctly.
509      *
510      * @throws Exception
511      */
512     @Test
testReadWifiConfigStoreData()513     public void testReadWifiConfigStoreData() throws Exception {
514         // Setup network list.
515         NetworkListStoreData networkList = new NetworkListUserStoreData(mContext);
516         mWifiConfigStore.registerStoreData(networkList);
517         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
518         openNetwork.creatorName = TEST_CREATOR_NAME;
519         openNetwork.setIpConfiguration(
520                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
521         openNetwork.setRandomizedMacAddress(TEST_RANDOMIZED_MAC);
522         List<WifiConfiguration> userConfigs = new ArrayList<>();
523         userConfigs.add(openNetwork);
524 
525         // Setup deleted ephemeral SSID list.
526         DeletedEphemeralSsidsStoreData deletedEphemeralSsids =
527                 new DeletedEphemeralSsidsStoreData(mClock);
528         mWifiConfigStore.registerStoreData(deletedEphemeralSsids);
529         String testSsid = "\"Test SSID\"";
530         Map<String, Long> ssidMap = new HashMap<>();
531         ssidMap.put(testSsid, 0L);
532 
533         // Setup user store XML bytes.
534         String xmlString = String.format(TEST_DATA_XML_STRING_FORMAT,
535                 openNetwork.configKey().replaceAll("\"", "&quot;"),
536                 openNetwork.SSID.replaceAll("\"", "&quot;"),
537                 openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName,
538                 openNetwork.getRandomizedMacAddress(), testSsid.replaceAll("\"", "&quot;"));
539         byte[] xmlBytes = xmlString.getBytes(StandardCharsets.UTF_8);
540         mUserStore.storeRawDataToWrite(xmlBytes);
541 
542         mWifiConfigStore.switchUserStoresAndRead(mUserStores);
543         WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore(
544                 userConfigs, networkList.getConfigurations());
545         assertEquals(ssidMap, deletedEphemeralSsids.getSsidToTimeMap());
546     }
547 
548     /**
549      * Verify that the WiFi configuration store data containing network list and deleted
550      * ephemeral SSID list are serialized correctly, matches the predefined test XML data.
551      *
552      * @throws Exception
553      */
554     @Test
testWriteWifiConfigStoreData()555     public void testWriteWifiConfigStoreData() throws Exception {
556         // Setup user store.
557         mWifiConfigStore.switchUserStoresAndRead(mUserStores);
558 
559         // Setup network list store data.
560         NetworkListStoreData networkList = new NetworkListUserStoreData(mContext);
561         mWifiConfigStore.registerStoreData(networkList);
562         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
563         openNetwork.creatorName = TEST_CREATOR_NAME;
564         openNetwork.setIpConfiguration(
565                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
566         openNetwork.setRandomizedMacAddress(TEST_RANDOMIZED_MAC);
567         List<WifiConfiguration> userConfigs = new ArrayList<>();
568         userConfigs.add(openNetwork);
569         networkList.setConfigurations(userConfigs);
570 
571         // Setup deleted ephemeral SSID list store data.
572         DeletedEphemeralSsidsStoreData deletedEphemeralSsids =
573                 new DeletedEphemeralSsidsStoreData(mClock);
574         mWifiConfigStore.registerStoreData(deletedEphemeralSsids);
575         String testSsid = "Test SSID";
576         Map<String, Long> ssidMap = new HashMap<>();
577         ssidMap.put(testSsid, 0L);
578         deletedEphemeralSsids.setSsidToTimeMap(ssidMap);
579 
580         // Setup expected XML bytes.
581         String xmlString = String.format(TEST_DATA_XML_STRING_FORMAT,
582                 openNetwork.configKey().replaceAll("\"", "&quot;"),
583                 openNetwork.SSID.replaceAll("\"", "&quot;"),
584                 openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName,
585                 openNetwork.getRandomizedMacAddress(), testSsid.replaceAll("\"", "&quot;"));
586 
587         mWifiConfigStore.write(true);
588         // Verify the user store content.
589         assertEquals(xmlString, new String(mUserStore.getStoreBytes()));
590     }
591 
592     /**
593      * Verify that a store file contained WiFi configuration store data (network list and
594      * deleted ephemeral SSID list) using the predefined test XML data is read and parsed
595      * correctly.
596      *
597      * @throws Exception
598      */
599     @Test
testReadWifiConfigStoreDataIndicateClientsThatThereIsNoDataForThem()600     public void testReadWifiConfigStoreDataIndicateClientsThatThereIsNoDataForThem()
601             throws Exception {
602         // Set both the user store & shared store files.
603         mWifiConfigStore.switchUserStoresAndRead(mUserStores);
604 
605         String storeData1Name = "test1";
606         String storeData2Name = "test2";
607         StoreData storeData1 = mock(StoreData.class);
608         StoreData storeData2 = mock(StoreData.class);
609 
610         assertTrue(mWifiConfigStore.registerStoreData(storeData1));
611         assertTrue(mWifiConfigStore.registerStoreData(storeData2));
612 
613         String fileContentsXmlStringWithOnlyStoreData1 =
614                 String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE, storeData1Name);
615         String fileContentsXmlStringWithOnlyStoreData2 =
616                 String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE, storeData2Name);
617         String fileContentsXmlStringWithStoreData1AndStoreData2 =
618                 String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_TWO_DATA_SOURCE,
619                         storeData1Name, storeData2Name);
620 
621         // Scenario 1: StoreData1 in shared store file.
622         when(storeData1.getName()).thenReturn(storeData1Name);
623         when(storeData2.getName()).thenReturn(storeData2Name);
624         when(storeData1.getStoreFileId())
625                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
626         when(storeData2.getStoreFileId())
627                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
628         mSharedStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData1.getBytes());
629         mUserStore.storeRawDataToWrite(null);
630 
631         mWifiConfigStore.read();
632         verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any());
633         verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
634         verify(storeData2).deserializeData(eq(null), anyInt(), anyInt(), any());
635         reset(storeData1, storeData2);
636 
637         // Scenario 2: StoreData2 in user store file.
638         when(storeData1.getName()).thenReturn(storeData1Name);
639         when(storeData2.getName()).thenReturn(storeData2Name);
640         when(storeData1.getStoreFileId())
641                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
642         when(storeData2.getStoreFileId())
643                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
644         mSharedStore.storeRawDataToWrite(null);
645         mUserStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData2.getBytes());
646 
647         mWifiConfigStore.read();
648         verify(storeData1).deserializeData(eq(null), anyInt(), anyInt(), any());
649         verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any());
650         verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
651         reset(storeData1, storeData2);
652 
653         // Scenario 3: StoreData1 in shared store file & StoreData2 in user store file.
654         when(storeData1.getName()).thenReturn(storeData1Name);
655         when(storeData2.getName()).thenReturn(storeData2Name);
656         when(storeData1.getStoreFileId())
657                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
658         when(storeData2.getStoreFileId())
659                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
660         mSharedStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData1.getBytes());
661         mUserStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData2.getBytes());
662 
663         mWifiConfigStore.read();
664         verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any());
665         verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
666         verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any());
667         verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
668         reset(storeData1, storeData2);
669 
670         // Scenario 4: StoreData1 & StoreData2 in shared store file.
671         when(storeData1.getName()).thenReturn(storeData1Name);
672         when(storeData2.getName()).thenReturn(storeData2Name);
673         when(storeData1.getStoreFileId())
674                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
675         when(storeData2.getStoreFileId())
676                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
677         mSharedStore.storeRawDataToWrite(
678                 fileContentsXmlStringWithStoreData1AndStoreData2.getBytes());
679         mUserStore.storeRawDataToWrite(null);
680 
681         mWifiConfigStore.read();
682         verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any());
683         verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
684         verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any());
685         verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
686         reset(storeData1, storeData2);
687     }
688 
689     /**
690      * Tests the write API behavior when all the store data's registered for a given store file
691      * has no new data to write.
692      * Expected behaviour: The write should not trigger a new file write for that specific store
693      * file.
694      */
695     @Test
testWriteWithNoNewData()696     public void testWriteWithNoNewData() throws Exception {
697         StoreData sharedStoreData = mock(StoreData.class);
698         when(sharedStoreData.getStoreFileId())
699                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
700         when(sharedStoreData.hasNewDataToSerialize()).thenReturn(true);
701         when(sharedStoreData.getName()).thenReturn("sharedStoreData");
702 
703         StoreData userStoreData = mock(StoreData.class);
704         when(userStoreData.getStoreFileId())
705                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
706         when(userStoreData.hasNewDataToSerialize()).thenReturn(true);
707         when(userStoreData.getName()).thenReturn("userStoreData");
708 
709         StoreData userStoreNetworkSuggestionsData =
710                 mock(StoreData.class);
711         when(userStoreNetworkSuggestionsData.getStoreFileId())
712                 .thenReturn(WifiConfigStore.STORE_FILE_USER_NETWORK_SUGGESTIONS);
713         when(userStoreNetworkSuggestionsData.hasNewDataToSerialize()).thenReturn(false);
714         when(userStoreNetworkSuggestionsData.getName())
715                 .thenReturn("userStoreNetworkSuggestionsData");
716 
717         assertTrue(mWifiConfigStore.registerStoreData(sharedStoreData));
718         assertTrue(mWifiConfigStore.registerStoreData(userStoreData));
719         assertTrue(mWifiConfigStore.registerStoreData(userStoreNetworkSuggestionsData));
720 
721         // Write both share and user config store.
722         mWifiConfigStore.setUserStores(mUserStores);
723 
724         // Now trigger a write.
725         mWifiConfigStore.write(true);
726 
727         verify(sharedStoreData).hasNewDataToSerialize();
728         verify(userStoreData).hasNewDataToSerialize();
729         verify(userStoreNetworkSuggestionsData).hasNewDataToSerialize();
730 
731         // Verify that we serialized data from the first 2 data source, but not from the last one.
732         verify(sharedStoreData).serializeData(any(), any());
733         verify(userStoreData).serializeData(any(), any());
734         verify(userStoreNetworkSuggestionsData, never()).serializeData(any(), any());
735     }
736 
737     /**
738      * Verify that a XmlPullParserException will be thrown when reading an user store file
739      * containing unknown data.
740      *
741      * @throws Exception
742      */
743     @Test(expected = XmlPullParserException.class)
testReadUserStoreContainedUnknownData()744     public void testReadUserStoreContainedUnknownData() throws Exception {
745         String storeFileData =
746                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
747                         + "<WifiConfigStoreData>\n"
748                         + "<int name=\"Version\" value=\"1\" />\n"
749                         + "<UnknownTag>\n"    // No StoreData registered to handle this tag.
750                         + "</UnknownTag>\n"
751                         + "</WifiConfigStoreData>\n";
752         mUserStore.storeRawDataToWrite(storeFileData.getBytes(StandardCharsets.UTF_8));
753         mWifiConfigStore.switchUserStoresAndRead(mUserStores);
754     }
755 
756     /**
757      * Verify that a XmlPullParserException will be thrown when reading the share store file
758      * containing unknown data.
759      *
760      * @throws Exception
761      */
762     @Test(expected = XmlPullParserException.class)
testReadShareStoreContainedUnknownData()763     public void testReadShareStoreContainedUnknownData() throws Exception {
764         String storeFileData =
765                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
766                         + "<WifiConfigStoreData>\n"
767                         + "<int name=\"Version\" value=\"1\" />\n"
768                         + "<UnknownTag>\n"    // No StoreData registered to handle this tag.
769                         + "</UnknownTag>\n"
770                         + "</WifiConfigStoreData>\n";
771         mSharedStore.storeRawDataToWrite(storeFileData.getBytes(StandardCharsets.UTF_8));
772         mWifiConfigStore.read();
773     }
774 
775     /**
776      * Tests the read API behaviour when the config store file is version 1.
777      * Expected behaviour: The read should be successful and send the data to the corresponding
778      *                     {@link StoreData} instance.
779      */
780     @Test
testReadVersion1StoreFile()781     public void testReadVersion1StoreFile() throws Exception {
782         // Register data container.
783         StoreData sharedStoreData = mock(StoreData.class);
784         when(sharedStoreData.getStoreFileId())
785                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
786         when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA);
787         StoreData userStoreData = mock(StoreData.class);
788         when(userStoreData.getStoreFileId())
789                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
790         when(userStoreData.getName()).thenReturn(TEST_USER_DATA);
791         mWifiConfigStore.registerStoreData(sharedStoreData);
792         mWifiConfigStore.registerStoreData(userStoreData);
793 
794         // Read both share and user config store.
795         mWifiConfigStore.setUserStores(mUserStores);
796 
797         // Now store some content in the shared and user data files.
798         mUserStore.storeRawDataToWrite(
799                 String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE,
800                         TEST_USER_DATA).getBytes());
801         mSharedStore.storeRawDataToWrite(
802                 String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE,
803                         TEST_SHARE_DATA).getBytes());
804 
805         // Read and verify the data content in the store file (metadata stripped out) has been sent
806         // to the corresponding store data when integrity check passes.
807         mWifiConfigStore.read();
808         verify(sharedStoreData, times(1)).deserializeData(
809                 any(XmlPullParser.class), anyInt(),
810                 eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any());
811         verify(userStoreData, times(1)).deserializeData(
812                 any(XmlPullParser.class), anyInt(),
813                 eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any());
814     }
815 
816     /**
817      * Tests the read API behaviour to ensure that the integrity data is parsed from the file.
818      */
819     @Test
testReadVersion2StoreFile()820     public void testReadVersion2StoreFile() throws Exception {
821         byte[] encryptedData = new byte[0];
822         byte[] iv = new byte[0];
823         Random random = new Random();
824         random.nextBytes(encryptedData);
825         random.nextBytes(iv);
826 
827         // Register data container.
828         StoreData sharedStoreData = mock(StoreData.class);
829         when(sharedStoreData.getStoreFileId())
830                 .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
831         when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA);
832         when(sharedStoreData.hasNewDataToSerialize()).thenReturn(true);
833         StoreData userStoreData = mock(StoreData.class);
834         when(userStoreData.getStoreFileId())
835                 .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
836         when(userStoreData.getName()).thenReturn(TEST_USER_DATA);
837         when(userStoreData.hasNewDataToSerialize()).thenReturn(true);
838         mWifiConfigStore.registerStoreData(sharedStoreData);
839         mWifiConfigStore.registerStoreData(userStoreData);
840 
841         // Read both share and user config store.
842         mWifiConfigStore.setUserStores(mUserStores);
843 
844         // Now store some content in the shared and user data files with encrypted data from above.
845         mUserStore.storeRawDataToWrite(
846                 String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE,
847                         HexEncoding.encodeToString(encryptedData),
848                         HexEncoding.encodeToString(iv),
849                         TEST_USER_DATA).getBytes());
850         mSharedStore.storeRawDataToWrite(
851                 String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE,
852                         HexEncoding.encodeToString(encryptedData),
853                         HexEncoding.encodeToString(iv),
854                         TEST_SHARE_DATA).getBytes());
855 
856         // Read and verify the data content in the store file (metadata stripped out) has been sent
857         // to the corresponding store data.
858         mWifiConfigStore.read();
859         verify(sharedStoreData, times(1))
860                 .deserializeData(any(XmlPullParser.class), anyInt(),
861                         eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any());
862         verify(userStoreData, times(1))
863                 .deserializeData(any(XmlPullParser.class), anyInt(),
864                         eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any());
865     }
866 
867     /**
868      * Mock Store File to redirect all file writes from WifiConfigStore to local buffers.
869      * This can be used to examine the data output by WifiConfigStore.
870      */
871     private class MockStoreFile extends StoreFile {
872         private byte[] mStoreBytes;
873         private boolean mStoreWritten;
874 
MockStoreFile(@ifiConfigStore.StoreFileId int fileId)875         MockStoreFile(@WifiConfigStore.StoreFileId int fileId) {
876             super(new File("MockStoreFile"), fileId, mEncryptionUtil);
877         }
878 
879         @Override
readRawData()880         public byte[] readRawData() {
881             return mStoreBytes;
882         }
883 
884         @Override
storeRawDataToWrite(byte[] data)885         public void storeRawDataToWrite(byte[] data) {
886             mStoreBytes = data;
887             mStoreWritten = false;
888         }
889 
890         @Override
exists()891         public boolean exists() {
892             return (mStoreBytes != null);
893         }
894 
895         @Override
writeBufferedRawData()896         public void writeBufferedRawData() {
897             if (!ArrayUtils.isEmpty(mStoreBytes)) {
898                 mStoreWritten = true;
899             }
900         }
901 
getStoreBytes()902         public byte[] getStoreBytes() {
903             return mStoreBytes;
904         }
905 
isStoreWritten()906         public boolean isStoreWritten() {
907             return mStoreWritten;
908         }
909     }
910 
911     /**
912      * Mock data container for providing test data for the store file.
913      */
914     private class MockStoreData implements StoreData {
915         private static final String XML_TAG_TEST_HEADER = "TestHeader";
916         private static final String XML_TAG_TEST_DATA = "TestData";
917 
918         private @WifiConfigStore.StoreFileId int mFileId;
919         private String mData;
920         private boolean mHasAnyNewData = true;
921 
MockStoreData(@ifiConfigStore.StoreFileId int fileId)922         MockStoreData(@WifiConfigStore.StoreFileId int fileId) {
923             mFileId = fileId;
924         }
925 
926         @Override
serializeData(XmlSerializer out, WifiConfigStoreEncryptionUtil encryptionUtil)927         public void serializeData(XmlSerializer out, WifiConfigStoreEncryptionUtil encryptionUtil)
928                 throws XmlPullParserException, IOException {
929             XmlUtil.writeNextValue(out, XML_TAG_TEST_DATA, mData);
930         }
931 
932         @Override
deserializeData(XmlPullParser in, int outerTagDepth, int version, WifiConfigStoreEncryptionUtil encryptionUtil)933         public void deserializeData(XmlPullParser in, int outerTagDepth, int version,
934                 WifiConfigStoreEncryptionUtil encryptionUtil)
935                 throws XmlPullParserException, IOException {
936             if (in == null) {
937                 return;
938             }
939             mData = (String) XmlUtil.readNextValueWithName(in, XML_TAG_TEST_DATA);
940         }
941 
942         @Override
resetData()943         public void resetData() {
944             mData = null;
945         }
946 
947         @Override
hasNewDataToSerialize()948         public boolean hasNewDataToSerialize() {
949             return mHasAnyNewData;
950         }
951 
952         @Override
getName()953         public String getName() {
954             return XML_TAG_TEST_HEADER;
955         }
956 
957         @Override
getStoreFileId()958         public @WifiConfigStore.StoreFileId int getStoreFileId() {
959             return mFileId;
960         }
961 
getData()962         public String getData() {
963             return mData;
964         }
965 
setData(String data)966         public void setData(String data) {
967             mData = data;
968         }
969 
setHasAnyNewData(boolean hasAnyNewData)970         public void setHasAnyNewData(boolean hasAnyNewData) {
971             mHasAnyNewData = hasAnyNewData;
972         }
973     }
974 }
975