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("\"", """), 536 openNetwork.SSID.replaceAll("\"", """), 537 openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName, 538 openNetwork.getRandomizedMacAddress(), testSsid.replaceAll("\"", """)); 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("\"", """), 583 openNetwork.SSID.replaceAll("\"", """), 584 openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName, 585 openNetwork.getRandomizedMacAddress(), testSsid.replaceAll("\"", """)); 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