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("\"", """), 516 openNetwork.SSID.replaceAll("\"", """), 517 openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName, 518 openNetwork.getRandomizedMacAddress(), testSsid.replaceAll("\"", """)); 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("\"", """), 563 openNetwork.SSID.replaceAll("\"", """), 564 openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName, 565 openNetwork.getRandomizedMacAddress(), testSsid.replaceAll("\"", """)); 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