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