/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi; import static com.android.server.wifi.WifiConfigStore.ZEROED_ENCRYPTED_DATA; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import android.app.test.MockAnswerUtil.AnswerWithArguments; import android.app.test.TestAlarmManager; import android.content.Context; import android.content.pm.PackageManager; import android.net.MacAddress; import android.net.wifi.WifiConfiguration; import android.os.test.TestLooper; import androidx.test.filters.SmallTest; import com.android.internal.util.ArrayUtils; import com.android.server.wifi.WifiConfigStore.StoreData; import com.android.server.wifi.WifiConfigStore.StoreFile; import com.android.server.wifi.util.DataIntegrityChecker; import com.android.server.wifi.util.EncryptedData; import com.android.server.wifi.util.XmlUtil; import libcore.util.HexEncoding; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; /** * Unit tests for {@link com.android.server.wifi.WifiConfigStore}. */ @SmallTest public class WifiConfigStoreTest { private static final String TEST_USER_DATA = "UserData"; private static final String TEST_SHARE_DATA = "ShareData"; private static final String TEST_CREATOR_NAME = "CreatorName"; private static final MacAddress TEST_RANDOMIZED_MAC = MacAddress.fromString("da:a1:19:c4:26:fa"); private static final String TEST_DATA_XML_STRING_FORMAT = "\n" + "\n" + "\n" + "\n" + "000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000" + "\n" + "000000000000000000000000\n" + "\n" + "\n" + "\n" + "\n" + "%s\n" + "%s\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "01\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "%s\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "%s\n" + "\n" + "\n" + "\n" + "NETWORK_SELECTION_ENABLED\n" + "NETWORK_SELECTION_ENABLE\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "DHCP\n" + "NONE\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n"; private static final String TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE = "\n" + "\n" + "\n" + "<%s/>n" + "\n"; private static final String TEST_DATA_XML_STRING_FORMAT_V1_WITH_TWO_DATA_SOURCE = "\n" + "\n" + "\n" + "<%s/>n" + "<%s/>n" + "\n"; private static final String TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE = "\n" + "\n" + "\n" + "\n" + "%s\n" + "%s\n" + "\n" + "<%s />\n" + "\n"; // Test mocks @Mock private Context mContext; @Mock private PackageManager mPackageManager; private TestAlarmManager mAlarmManager; private TestLooper mLooper; @Mock private Clock mClock; @Mock private WifiMetrics mWifiMetrics; @Mock private DataIntegrityChecker mDataIntegrityChecker; private MockStoreFile mSharedStore; private MockStoreFile mUserStore; private MockStoreFile mUserNetworkSuggestionsStore; private List mUserStores = new ArrayList(); private MockStoreData mSharedStoreData; private MockStoreData mUserStoreData; /** * Test instance of WifiConfigStore. */ private WifiConfigStore mWifiConfigStore; /** * Setup mocks before the test starts. */ private void setupMocks() throws Exception { MockitoAnnotations.initMocks(this); mAlarmManager = new TestAlarmManager(); mLooper = new TestLooper(); when(mContext.getSystemService(Context.ALARM_SERVICE)) .thenReturn(mAlarmManager.getAlarmManager()); when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getNameForUid(anyInt())).thenReturn(TEST_CREATOR_NAME); when(mDataIntegrityChecker.compute(any(byte[].class))) .thenReturn(ZEROED_ENCRYPTED_DATA); when(mDataIntegrityChecker.isOk(any(byte[].class), any(EncryptedData.class))) .thenReturn(true); mSharedStore = new MockStoreFile(WifiConfigStore.STORE_FILE_SHARED_GENERAL); mUserStore = new MockStoreFile(WifiConfigStore.STORE_FILE_USER_GENERAL); mUserNetworkSuggestionsStore = new MockStoreFile(WifiConfigStore.STORE_FILE_USER_NETWORK_SUGGESTIONS); mUserStores.add(mUserStore); mUserStores.add(mUserNetworkSuggestionsStore); mSharedStoreData = new MockStoreData(WifiConfigStore.STORE_FILE_SHARED_GENERAL); mUserStoreData = new MockStoreData(WifiConfigStore.STORE_FILE_USER_GENERAL); } /** * Setup the test environment. */ @Before public void setUp() throws Exception { setupMocks(); mWifiConfigStore = new WifiConfigStore(mContext, mLooper.getLooper(), mClock, mWifiMetrics, mSharedStore); // Enable verbose logging before tests. mWifiConfigStore.enableVerboseLogging(true); } /** * Called after each test */ @After public void cleanup() { validateMockitoUsage(); } /** * Verify that no write occurs if there is {@link StoreData} registered for any * {@link StoreFile}. * * @throws Exception */ @Test public void testWriteWithNoStoreData() throws Exception { // Perform force write to both share and user store file. mWifiConfigStore.setUserStores(mUserStores); mWifiConfigStore.write(true); assertFalse(mSharedStore.isStoreWritten()); assertFalse(mUserStore.isStoreWritten()); assertFalse(mUserNetworkSuggestionsStore.isStoreWritten()); verify(mWifiMetrics, never()).noteWifiConfigStoreWriteDuration(anyInt()); } /** * Tests the write API with the force flag set to true. * Expected behavior: This should trigger an immediate write to the store files and no alarms * should be started. */ @Test public void testForceWrite() throws Exception { // Register data container. mWifiConfigStore.registerStoreData(mSharedStoreData); mWifiConfigStore.registerStoreData(mUserStoreData); mWifiConfigStore.switchUserStoresAndRead(mUserStores); mWifiConfigStore.write(true); assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG)); assertTrue(mSharedStore.isStoreWritten()); assertTrue(mUserStore.isStoreWritten()); assertFalse(mUserNetworkSuggestionsStore.isStoreWritten()); verify(mWifiMetrics).noteWifiConfigStoreWriteDuration(anyInt()); } /** * Tests the write API with the force flag set to false. * Expected behavior: This should set an alarm to write to the store files. */ @Test public void testBufferedWrite() throws Exception { // Register data container. mWifiConfigStore.registerStoreData(mSharedStoreData); mWifiConfigStore.registerStoreData(mUserStoreData); mWifiConfigStore.switchUserStoresAndRead(mUserStores); mWifiConfigStore.write(false); assertTrue(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG)); assertFalse(mSharedStore.isStoreWritten()); assertFalse(mUserStore.isStoreWritten()); assertFalse(mUserNetworkSuggestionsStore.isStoreWritten()); // Now send the alarm and ensure that the writes happen. mAlarmManager.dispatch(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG); mLooper.dispatchAll(); assertTrue(mSharedStore.isStoreWritten()); assertTrue(mUserStore.isStoreWritten()); assertFalse(mUserNetworkSuggestionsStore.isStoreWritten()); verify(mWifiMetrics).noteWifiConfigStoreWriteDuration(anyInt()); } /** * Tests the force write after a buffered write. * Expected behaviour: The force write should override the previous buffered write and stop the * buffer write alarms. */ @Test public void testForceWriteAfterBufferedWrite() throws Exception { // Register a test data container with bogus data. mWifiConfigStore.registerStoreData(mSharedStoreData); mWifiConfigStore.registerStoreData(mUserStoreData); mSharedStoreData.setData("abcds"); mUserStoreData.setData("asdfa"); // Perform buffered write for both user and share store file. mWifiConfigStore.switchUserStoresAndRead(mUserStores); mWifiConfigStore.write(false); assertTrue(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG)); assertFalse(mSharedStore.isStoreWritten()); assertFalse(mUserStore.isStoreWritten()); // Update the container with new set of data. The send a force write and ensure that the // writes have been performed and alarms have been stopped and updated data are written. mUserStoreData.setData(TEST_USER_DATA); mSharedStoreData.setData(TEST_SHARE_DATA); mWifiConfigStore.write(true); assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG)); assertTrue(mSharedStore.isStoreWritten()); assertTrue(mUserStore.isStoreWritten()); // Verify correct data are loaded to the data container after a read. mWifiConfigStore.read(); assertEquals(TEST_USER_DATA, mUserStoreData.getData()); assertEquals(TEST_SHARE_DATA, mSharedStoreData.getData()); } /** * Tests the force write with no new data after a buffered write. * Expected behaviour: The force write should flush the previous buffered write and stop the * buffer write alarms. */ @Test public void testForceWriteWithNoNewDataAfterBufferedWrite() throws Exception { // Register a test data container with bogus data. mWifiConfigStore.registerStoreData(mSharedStoreData); mWifiConfigStore.registerStoreData(mUserStoreData); mSharedStoreData.setData("abcds"); mUserStoreData.setData("asdfa"); // Perform buffered write for both user and share store file. mWifiConfigStore.switchUserStoresAndRead(mUserStores); mWifiConfigStore.write(false); assertTrue(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG)); assertFalse(mSharedStore.isStoreWritten()); assertFalse(mUserStore.isStoreWritten()); // Containers have no new data. mUserStoreData.setHasAnyNewData(false); mSharedStoreData.setHasAnyNewData(false); mWifiConfigStore.write(true); assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG)); assertTrue(mSharedStore.isStoreWritten()); assertTrue(mUserStore.isStoreWritten()); // Verify correct data are loaded to the data container after a read. mWifiConfigStore.read(); assertEquals("abcds", mSharedStoreData.getData()); assertEquals("asdfa", mUserStoreData.getData()); } /** * Tests the read API behaviour after a write to the store files. * Expected behaviour: The read should return the same data that was last written. */ @Test public void testReadAfterWrite() throws Exception { // Register data container. mWifiConfigStore.registerStoreData(mSharedStoreData); mWifiConfigStore.registerStoreData(mUserStoreData); // Read both share and user config store. mWifiConfigStore.switchUserStoresAndRead(mUserStores); // Verify no data is read. assertNull(mUserStoreData.getData()); assertNull(mSharedStoreData.getData()); // Write share and user data. mUserStoreData.setData(TEST_USER_DATA); mSharedStoreData.setData(TEST_SHARE_DATA); mWifiConfigStore.write(true); // Read and verify the data content in the data container. mWifiConfigStore.read(); assertEquals(TEST_USER_DATA, mUserStoreData.getData()); assertEquals(TEST_SHARE_DATA, mSharedStoreData.getData()); verify(mWifiMetrics, times(2)).noteWifiConfigStoreReadDuration(anyInt()); verify(mWifiMetrics).noteWifiConfigStoreWriteDuration(anyInt()); } /** * Tests the read API behaviour when the shared store file is empty and the user store * is not yet visible (user not yet unlocked). * Expected behaviour: The read should return an empty store data instance when the file not * found exception is raised. */ @Test public void testReadWithNoSharedStoreFileAndUserStoreNotVisible() throws Exception { StoreData sharedStoreData = mock(StoreData.class); when(sharedStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); StoreData userStoreData = mock(StoreData.class); when(userStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); // Reading the mock store without a write should simulate the file not found case because // |readRawData| would return null. mWifiConfigStore.registerStoreData(sharedStoreData); mWifiConfigStore.registerStoreData(userStoreData); assertFalse(mWifiConfigStore.areStoresPresent()); mWifiConfigStore.read(); // Ensure that we got the call to deserialize empty shared data, but no user data. verify(sharedStoreData).resetData(); verify(sharedStoreData).deserializeData(eq(null), anyInt()); verify(userStoreData, never()).resetData(); verify(userStoreData, never()).deserializeData(any(), anyInt()); } /** * Tests the read API behaviour when there are no user/shared store files on the device. * Expected behaviour: The read should return an empty store data instance when the file not * found exception is raised. */ @Test public void testReadWithNoStoreFiles() throws Exception { StoreData sharedStoreData = mock(StoreData.class); when(sharedStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); StoreData userStoreData = mock(StoreData.class); when(userStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); // Reading the mock store without a write should simulate the file not found case because // |readRawData| would return null. mWifiConfigStore.registerStoreData(sharedStoreData); mWifiConfigStore.registerStoreData(userStoreData); // Read both share and user config store. mWifiConfigStore.setUserStores(mUserStores); assertFalse(mWifiConfigStore.areStoresPresent()); mWifiConfigStore.read(); // Ensure that we got the call to deserialize empty shared & user data. verify(userStoreData).resetData(); verify(userStoreData).deserializeData(eq(null), anyInt()); verify(sharedStoreData).resetData(); verify(sharedStoreData).deserializeData(eq(null), anyInt()); } /** * Tests the read API behaviour after a write to the shared store file when the user * store file is null. * Expected behaviour: The read should return the same data that was last written. */ @Test public void testReadAfterWriteWithNoUserStore() throws Exception { // Setup data container. mWifiConfigStore.registerStoreData(mSharedStoreData); mSharedStoreData.setData(TEST_SHARE_DATA); // Perform write for the share store file. mWifiConfigStore.write(true); mWifiConfigStore.read(); // Verify data content for both user and share data. assertEquals(TEST_SHARE_DATA, mSharedStoreData.getData()); } /** * Verifies that a read operation will reset the data in the data container, to avoid * any stale data from previous read. * * @throws Exception */ @Test public void testReadWillResetStoreData() throws Exception { // Register and setup store data. mWifiConfigStore.registerStoreData(mSharedStoreData); mWifiConfigStore.registerStoreData(mUserStoreData); // Perform force write with empty data content to both user and share store file. mWifiConfigStore.switchUserStoresAndRead(mUserStores); mWifiConfigStore.write(true); // Setup data container with some value. mUserStoreData.setData(TEST_USER_DATA); mSharedStoreData.setData(TEST_SHARE_DATA); // Perform read of both user and share store file and verify data in the data container // is in sync (empty) with what is in the file. mWifiConfigStore.read(); assertNull(mSharedStoreData.getData()); assertNull(mUserStoreData.getData()); } /** * Verify that a store file contained WiFi configuration store data (network list and * deleted ephemeral SSID list) using the predefined test XML data is read and parsed * correctly. * * @throws Exception */ @Test public void testReadWifiConfigStoreData() throws Exception { // Setup network list. NetworkListStoreData networkList = new NetworkListUserStoreData(mContext); mWifiConfigStore.registerStoreData(networkList); WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(); openNetwork.creatorName = TEST_CREATOR_NAME; openNetwork.setIpConfiguration( WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy()); openNetwork.setRandomizedMacAddress(TEST_RANDOMIZED_MAC); List userConfigs = new ArrayList<>(); userConfigs.add(openNetwork); // Setup deleted ephemeral SSID list. DeletedEphemeralSsidsStoreData deletedEphemeralSsids = new DeletedEphemeralSsidsStoreData(mClock); mWifiConfigStore.registerStoreData(deletedEphemeralSsids); String testSsid = "\"Test SSID\""; Map ssidMap = new HashMap<>(); ssidMap.put(testSsid, 0L); // Setup user store XML bytes. String xmlString = String.format(TEST_DATA_XML_STRING_FORMAT, openNetwork.configKey().replaceAll("\"", """), openNetwork.SSID.replaceAll("\"", """), openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName, openNetwork.getRandomizedMacAddress(), testSsid.replaceAll("\"", """)); byte[] xmlBytes = xmlString.getBytes(StandardCharsets.UTF_8); mUserStore.storeRawDataToWrite(xmlBytes); mWifiConfigStore.switchUserStoresAndRead(mUserStores); WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore( userConfigs, networkList.getConfigurations()); assertEquals(ssidMap, deletedEphemeralSsids.getSsidToTimeMap()); } /** * Verify that the WiFi configuration store data containing network list and deleted * ephemeral SSID list are serialized correctly, matches the predefined test XML data. * * @throws Exception */ @Test public void testWriteWifiConfigStoreData() throws Exception { // Setup user store. mWifiConfigStore.switchUserStoresAndRead(mUserStores); // Setup network list store data. NetworkListStoreData networkList = new NetworkListUserStoreData(mContext); mWifiConfigStore.registerStoreData(networkList); WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(); openNetwork.creatorName = TEST_CREATOR_NAME; openNetwork.setIpConfiguration( WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy()); openNetwork.setRandomizedMacAddress(TEST_RANDOMIZED_MAC); List userConfigs = new ArrayList<>(); userConfigs.add(openNetwork); networkList.setConfigurations(userConfigs); // Setup deleted ephemeral SSID list store data. DeletedEphemeralSsidsStoreData deletedEphemeralSsids = new DeletedEphemeralSsidsStoreData(mClock); mWifiConfigStore.registerStoreData(deletedEphemeralSsids); String testSsid = "Test SSID"; Map ssidMap = new HashMap<>(); ssidMap.put(testSsid, 0L); deletedEphemeralSsids.setSsidToTimeMap(ssidMap); // Setup expected XML bytes. String xmlString = String.format(TEST_DATA_XML_STRING_FORMAT, openNetwork.configKey().replaceAll("\"", """), openNetwork.SSID.replaceAll("\"", """), openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName, openNetwork.getRandomizedMacAddress(), testSsid.replaceAll("\"", """)); mWifiConfigStore.write(true); // Verify the user store content. assertEquals(xmlString, new String(mUserStore.getStoreBytes())); } /** * Verify that a store file contained WiFi configuration store data (network list and * deleted ephemeral SSID list) using the predefined test XML data is read and parsed * correctly. * * @throws Exception */ @Test public void testReadWifiConfigStoreDataIndicateClientsThatThereIsNoDataForThem() throws Exception { // Set both the user store & shared store files. mWifiConfigStore.switchUserStoresAndRead(mUserStores); String storeData1Name = "test1"; String storeData2Name = "test2"; StoreData storeData1 = mock(StoreData.class); StoreData storeData2 = mock(StoreData.class); assertTrue(mWifiConfigStore.registerStoreData(storeData1)); assertTrue(mWifiConfigStore.registerStoreData(storeData2)); String fileContentsXmlStringWithOnlyStoreData1 = String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE, storeData1Name); String fileContentsXmlStringWithOnlyStoreData2 = String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE, storeData2Name); String fileContentsXmlStringWithStoreData1AndStoreData2 = String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_TWO_DATA_SOURCE, storeData1Name, storeData2Name); // Scenario 1: StoreData1 in shared store file. when(storeData1.getName()).thenReturn(storeData1Name); when(storeData2.getName()).thenReturn(storeData2Name); when(storeData1.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); when(storeData2.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); mSharedStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData1.getBytes()); mUserStore.storeRawDataToWrite(null); mWifiConfigStore.read(); verify(storeData1).deserializeData(notNull(), anyInt()); verify(storeData1, never()).deserializeData(eq(null), anyInt()); verify(storeData2).deserializeData(eq(null), anyInt()); reset(storeData1, storeData2); // Scenario 2: StoreData2 in user store file. when(storeData1.getName()).thenReturn(storeData1Name); when(storeData2.getName()).thenReturn(storeData2Name); when(storeData1.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); when(storeData2.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); mSharedStore.storeRawDataToWrite(null); mUserStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData2.getBytes()); mWifiConfigStore.read(); verify(storeData1).deserializeData(eq(null), anyInt()); verify(storeData2).deserializeData(notNull(), anyInt()); verify(storeData2, never()).deserializeData(eq(null), anyInt()); reset(storeData1, storeData2); // Scenario 3: StoreData1 in shared store file & StoreData2 in user store file. when(storeData1.getName()).thenReturn(storeData1Name); when(storeData2.getName()).thenReturn(storeData2Name); when(storeData1.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); when(storeData2.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); mSharedStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData1.getBytes()); mUserStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData2.getBytes()); mWifiConfigStore.read(); verify(storeData1).deserializeData(notNull(), anyInt()); verify(storeData1, never()).deserializeData(eq(null), anyInt()); verify(storeData2).deserializeData(notNull(), anyInt()); verify(storeData2, never()).deserializeData(eq(null), anyInt()); reset(storeData1, storeData2); // Scenario 4: StoreData1 & StoreData2 in shared store file. when(storeData1.getName()).thenReturn(storeData1Name); when(storeData2.getName()).thenReturn(storeData2Name); when(storeData1.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); when(storeData2.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); mSharedStore.storeRawDataToWrite( fileContentsXmlStringWithStoreData1AndStoreData2.getBytes()); mUserStore.storeRawDataToWrite(null); mWifiConfigStore.read(); verify(storeData1).deserializeData(notNull(), anyInt()); verify(storeData1, never()).deserializeData(eq(null), anyInt()); verify(storeData2).deserializeData(notNull(), anyInt()); verify(storeData2, never()).deserializeData(eq(null), anyInt()); reset(storeData1, storeData2); } /** * Tests the write API behavior when all the store data's registered for a given store file * has no new data to write. * Expected behaviour: The write should not trigger a new file write for that specific store * file. */ @Test public void testWriteWithNoNewData() throws Exception { StoreData sharedStoreData = mock(StoreData.class); when(sharedStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); when(sharedStoreData.hasNewDataToSerialize()).thenReturn(true); when(sharedStoreData.getName()).thenReturn("sharedStoreData"); StoreData userStoreData = mock(StoreData.class); when(userStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); when(userStoreData.hasNewDataToSerialize()).thenReturn(true); when(userStoreData.getName()).thenReturn("userStoreData"); StoreData userStoreNetworkSuggestionsData = mock(StoreData.class); when(userStoreNetworkSuggestionsData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_NETWORK_SUGGESTIONS); when(userStoreNetworkSuggestionsData.hasNewDataToSerialize()).thenReturn(false); when(userStoreNetworkSuggestionsData.getName()) .thenReturn("userStoreNetworkSuggestionsData"); assertTrue(mWifiConfigStore.registerStoreData(sharedStoreData)); assertTrue(mWifiConfigStore.registerStoreData(userStoreData)); assertTrue(mWifiConfigStore.registerStoreData(userStoreNetworkSuggestionsData)); // Write both share and user config store. mWifiConfigStore.setUserStores(mUserStores); // Now trigger a write. mWifiConfigStore.write(true); verify(sharedStoreData).hasNewDataToSerialize(); verify(userStoreData).hasNewDataToSerialize(); verify(userStoreNetworkSuggestionsData).hasNewDataToSerialize(); // Verify that we serialized data from the first 2 data source, but not from the last one. verify(sharedStoreData).serializeData(any()); verify(userStoreData).serializeData(any()); verify(userStoreNetworkSuggestionsData, never()).serializeData(any()); } /** * Verify that a XmlPullParserException will be thrown when reading an user store file * containing unknown data. * * @throws Exception */ @Test(expected = XmlPullParserException.class) public void testReadUserStoreContainedUnknownData() throws Exception { String storeFileData = "\n" + "\n" + "\n" + "\n" // No StoreData registered to handle this tag. + "\n" + "\n"; mUserStore.storeRawDataToWrite(storeFileData.getBytes(StandardCharsets.UTF_8)); mWifiConfigStore.switchUserStoresAndRead(mUserStores); } /** * Verify that a XmlPullParserException will be thrown when reading the share store file * containing unknown data. * * @throws Exception */ @Test(expected = XmlPullParserException.class) public void testReadShareStoreContainedUnknownData() throws Exception { String storeFileData = "\n" + "\n" + "\n" + "\n" // No StoreData registered to handle this tag. + "\n" + "\n"; mSharedStore.storeRawDataToWrite(storeFileData.getBytes(StandardCharsets.UTF_8)); mWifiConfigStore.read(); } /** * Tests the read API behaviour when the config store file is version 1. * Expected behaviour: The read should be successful and send the data to the corresponding * {@link StoreData} instance. */ @Test public void testReadVersion1StoreFile() throws Exception { // Register data container. StoreData sharedStoreData = mock(StoreData.class); when(sharedStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA); StoreData userStoreData = mock(StoreData.class); when(userStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); when(userStoreData.getName()).thenReturn(TEST_USER_DATA); mWifiConfigStore.registerStoreData(sharedStoreData); mWifiConfigStore.registerStoreData(userStoreData); // Read both share and user config store. mWifiConfigStore.setUserStores(mUserStores); // Now store some content in the shared and user data files. mUserStore.storeRawDataToWrite( String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE, TEST_USER_DATA).getBytes()); mSharedStore.storeRawDataToWrite( String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE, TEST_SHARE_DATA).getBytes()); // Read and verify the data content in the store file (metadata stripped out) has been sent // to the corresponding store data when integrity check passes. mWifiConfigStore.read(); verify(sharedStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); verify(userStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); // We shouldn't perform any data integrity checks on version 1 file. verifyZeroInteractions(mDataIntegrityChecker); } /** * Tests the read API behaviour when integrity check fails. * Expected behaviour: The read should return an empty store data. */ @Test public void testReadWhenIntegrityCheckFails() throws Exception { // Register data container. StoreData sharedStoreData = mock(StoreData.class); when(sharedStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA); StoreData userStoreData = mock(StoreData.class); when(userStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); when(userStoreData.getName()).thenReturn(TEST_USER_DATA); mWifiConfigStore.registerStoreData(sharedStoreData); mWifiConfigStore.registerStoreData(userStoreData); // Read both share and user config store. mWifiConfigStore.setUserStores(mUserStores); // Now store some content in the shared and user data files. mUserStore.storeRawDataToWrite( String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), TEST_USER_DATA).getBytes()); mSharedStore.storeRawDataToWrite( String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), TEST_SHARE_DATA).getBytes()); // Read and verify the data content in the store file (metadata stripped out) has been sent // to the corresponding store data when integrity check passes. mWifiConfigStore.read(); verify(sharedStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); verify(userStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); // Read and verify the data content in the store file (metadata stripped out) has not been // sent to the corresponding store data when integrity check fails. when(mDataIntegrityChecker.isOk(any(byte[].class), any(EncryptedData.class))) .thenReturn(false); mWifiConfigStore.read(); verify(sharedStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); verify(userStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); } /** * Tests the write API behaviour when integrity check fails. * Expected behaviour: The read should return an empty store data. */ @Test public void testWriteWhenIntegrityComputeFails() throws Exception { // Register data container. StoreData sharedStoreData = mock(StoreData.class); when(sharedStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA); when(sharedStoreData.hasNewDataToSerialize()).thenReturn(true); StoreData userStoreData = mock(StoreData.class); when(userStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); when(userStoreData.getName()).thenReturn(TEST_USER_DATA); when(userStoreData.hasNewDataToSerialize()).thenReturn(true); mWifiConfigStore.registerStoreData(sharedStoreData); mWifiConfigStore.registerStoreData(userStoreData); // Read both share and user config store. mWifiConfigStore.setUserStores(mUserStores); // Reset store file contents & ensure that the user and store data files are empty. mUserStore.storeRawDataToWrite(null); mSharedStore.storeRawDataToWrite(null); assertNull(mUserStore.getStoreBytes()); assertNull(mSharedStore.getStoreBytes()); // Write and verify that the data is written to the config store file when integrity // computation passes. mWifiConfigStore.write(true); assertNotNull(mUserStore.getStoreBytes()); assertNotNull(mSharedStore.getStoreBytes()); assertTrue(new String(mUserStore.getStoreBytes()).contains(TEST_USER_DATA)); assertTrue(new String(mSharedStore.getStoreBytes()).contains(TEST_SHARE_DATA)); // Reset store file contents & ensure that the user and store data files are empty. mUserStore.storeRawDataToWrite(null); mSharedStore.storeRawDataToWrite(null); assertNull(mUserStore.getStoreBytes()); assertNull(mSharedStore.getStoreBytes()); // Write and verify that the data is not written to the config store file when integrity // computation fails. when(mDataIntegrityChecker.compute(any(byte[].class))).thenReturn(null); mWifiConfigStore.write(true); assertNull(mUserStore.getStoreBytes()); assertNull(mSharedStore.getStoreBytes()); } /** * Tests the write API behaviour to ensure that the integrity data is written to the file. */ @Test public void testWriteContainsIntegrityData() throws Exception { byte[] encryptedData = new byte[EncryptedData.ENCRYPTED_DATA_LENGTH]; byte[] iv = new byte[EncryptedData.IV_LENGTH]; Random random = new Random(); random.nextBytes(encryptedData); random.nextBytes(iv); final EncryptedData testEncryptedData = new EncryptedData(encryptedData, iv); doAnswer(new AnswerWithArguments() { public EncryptedData answer(byte[] data) { String storeXmlString = new String(data); // Verify that we fill in zeros to the data when we compute integrity. if (storeXmlString.contains(TEST_SHARE_DATA)) { assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), TEST_SHARE_DATA), storeXmlString); } else if (storeXmlString.contains(TEST_USER_DATA)) { assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), TEST_USER_DATA), storeXmlString); } return testEncryptedData; } }).when(mDataIntegrityChecker).compute(any(byte[].class)); // Register data container. StoreData sharedStoreData = mock(StoreData.class); when(sharedStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA); when(sharedStoreData.hasNewDataToSerialize()).thenReturn(true); StoreData userStoreData = mock(StoreData.class); when(userStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); when(userStoreData.getName()).thenReturn(TEST_USER_DATA); when(userStoreData.hasNewDataToSerialize()).thenReturn(true); mWifiConfigStore.registerStoreData(sharedStoreData); mWifiConfigStore.registerStoreData(userStoreData); // Read both share and user config store. mWifiConfigStore.setUserStores(mUserStores); // Write and verify that the data is written to the config store file when integrity // computation passes. mWifiConfigStore.write(true); // Verify that we fill in zeros to the data when we computed integrity. verify(mDataIntegrityChecker, times(2)).compute(any(byte[].class)); // Verify the parsed integrity data assertNotNull(mUserStore.getStoreBytes()); assertNotNull(mSharedStore.getStoreBytes()); String userStoreXmlString = new String(mUserStore.getStoreBytes()); String sharedStoreXmlString = new String(mSharedStore.getStoreBytes()); assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, HexEncoding.encodeToString(encryptedData).toLowerCase(), HexEncoding.encodeToString(iv).toLowerCase(), TEST_USER_DATA), userStoreXmlString); assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, HexEncoding.encodeToString(encryptedData).toLowerCase(), HexEncoding.encodeToString(iv).toLowerCase(), TEST_SHARE_DATA), sharedStoreXmlString); } /** * Tests the read API behaviour to ensure that the integrity data is parsed from the file and * used for checking integrity of the file. */ @Test public void testReadParsesIntegrityData() throws Exception { byte[] encryptedData = new byte[EncryptedData.ENCRYPTED_DATA_LENGTH]; byte[] iv = new byte[EncryptedData.IV_LENGTH]; Random random = new Random(); random.nextBytes(encryptedData); random.nextBytes(iv); // Register data container. StoreData sharedStoreData = mock(StoreData.class); when(sharedStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL); when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA); when(sharedStoreData.hasNewDataToSerialize()).thenReturn(true); StoreData userStoreData = mock(StoreData.class); when(userStoreData.getStoreFileId()) .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL); when(userStoreData.getName()).thenReturn(TEST_USER_DATA); when(userStoreData.hasNewDataToSerialize()).thenReturn(true); mWifiConfigStore.registerStoreData(sharedStoreData); mWifiConfigStore.registerStoreData(userStoreData); // Read both share and user config store. mWifiConfigStore.setUserStores(mUserStores); // Now store some content in the shared and user data files with encrypted data from above. mUserStore.storeRawDataToWrite( String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, HexEncoding.encodeToString(encryptedData), HexEncoding.encodeToString(iv), TEST_USER_DATA).getBytes()); mSharedStore.storeRawDataToWrite( String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, HexEncoding.encodeToString(encryptedData), HexEncoding.encodeToString(iv), TEST_SHARE_DATA).getBytes()); // Read and verify the data content in the store file (metadata stripped out) has been sent // to the corresponding store data when integrity check passes. mWifiConfigStore.read(); verify(sharedStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); verify(userStoreData, times(1)).deserializeData(any(XmlPullParser.class), anyInt()); // Verify that we parsed the integrity data and used it for checking integrity of the file. ArgumentCaptor integrityCaptor = ArgumentCaptor.forClass(EncryptedData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(byte[].class); // Will be invoked twice for each file - shared & user store file. verify(mDataIntegrityChecker, times(2)).isOk( dataCaptor.capture(), integrityCaptor.capture()); // Verify the parsed integrity data assertEquals(2, integrityCaptor.getAllValues().size()); EncryptedData parsedEncryptedData1 = integrityCaptor.getAllValues().get(0); assertArrayEquals(encryptedData, parsedEncryptedData1.getEncryptedData()); assertArrayEquals(iv, parsedEncryptedData1.getIv()); EncryptedData parsedEncryptedData2 = integrityCaptor.getAllValues().get(1); assertArrayEquals(encryptedData, parsedEncryptedData2.getEncryptedData()); assertArrayEquals(iv, parsedEncryptedData2.getIv()); // Verify that we fill in zeros to the data when we performed integrity checked. assertEquals(2, dataCaptor.getAllValues().size()); String sharedStoreXmlStringWithZeroedIntegrity = new String(dataCaptor.getAllValues().get(0)); assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), TEST_SHARE_DATA), sharedStoreXmlStringWithZeroedIntegrity); String userStoreXmlStringWithZeroedIntegrity = new String(dataCaptor.getAllValues().get(1)); assertEquals(String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE, HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getEncryptedData()), HexEncoding.encodeToString(ZEROED_ENCRYPTED_DATA.getIv()), TEST_USER_DATA), userStoreXmlStringWithZeroedIntegrity); } /** * Mock Store File to redirect all file writes from WifiConfigStore to local buffers. * This can be used to examine the data output by WifiConfigStore. */ private class MockStoreFile extends StoreFile { private byte[] mStoreBytes; private boolean mStoreWritten; MockStoreFile(@WifiConfigStore.StoreFileId int fileId) { super(new File("MockStoreFile"), fileId, mDataIntegrityChecker); } @Override public byte[] readRawData() { return mStoreBytes; } @Override public void storeRawDataToWrite(byte[] data) { mStoreBytes = data; mStoreWritten = false; } @Override public boolean exists() { return (mStoreBytes != null); } @Override public void writeBufferedRawData() { if (!ArrayUtils.isEmpty(mStoreBytes)) { mStoreWritten = true; } } public byte[] getStoreBytes() { return mStoreBytes; } public boolean isStoreWritten() { return mStoreWritten; } } /** * Mock data container for providing test data for the store file. */ private class MockStoreData implements StoreData { private static final String XML_TAG_TEST_HEADER = "TestHeader"; private static final String XML_TAG_TEST_DATA = "TestData"; private @WifiConfigStore.StoreFileId int mFileId; private String mData; private boolean mHasAnyNewData = true; MockStoreData(@WifiConfigStore.StoreFileId int fileId) { mFileId = fileId; } @Override public void serializeData(XmlSerializer out) throws XmlPullParserException, IOException { XmlUtil.writeNextValue(out, XML_TAG_TEST_DATA, mData); } @Override public void deserializeData(XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException { if (in == null) { return; } mData = (String) XmlUtil.readNextValueWithName(in, XML_TAG_TEST_DATA); } @Override public void resetData() { mData = null; } @Override public boolean hasNewDataToSerialize() { return mHasAnyNewData; } @Override public String getName() { return XML_TAG_TEST_HEADER; } @Override public @WifiConfigStore.StoreFileId int getStoreFileId() { return mFileId; } public String getData() { return mData; } public void setData(String data) { mData = data; } public void setHasAnyNewData(boolean hasAnyNewData) { mHasAnyNewData = hasAnyNewData; } } }