/* * 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 org.junit.Assert.*; import static org.mockito.Mockito.*; import android.app.test.TestAlarmManager; import android.content.Context; import android.content.pm.PackageManager; import android.net.wifi.WifiConfiguration; import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; import com.android.server.wifi.WifiConfigStore.StoreFile; import com.android.server.wifi.util.XmlUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; 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.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Unit tests for {@link com.android.server.wifi.WifiConfigStore}. */ @SmallTest public class WifiConfigStoreTest { // Store file content without any data. private static final String EMPTY_FILE_CONTENT = "\n" + "\n" + "\n" + "\n"; 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 String TEST_DATA_XML_STRING_FORMAT = "\n" + "\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" + "%s\n" + "\n" + "\n" + "\n" + "\n" + "\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" + "%s\n" + "\n" + "\n" + "\n"; // Test mocks @Mock private Context mContext; @Mock private PackageManager mPackageManager; private TestAlarmManager mAlarmManager; private TestLooper mLooper; @Mock private Clock mClock; private MockStoreFile mSharedStore; private MockStoreFile mUserStore; private MockStoreData mStoreData; /** * 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); mUserStore = new MockStoreFile(); mSharedStore = new MockStoreFile(); mStoreData = new MockStoreData(); } /** * Setup the test environment. */ @Before public void setUp() throws Exception { setupMocks(); mWifiConfigStore = new WifiConfigStore(mContext, mLooper.getLooper(), mClock, mSharedStore); // Enable verbose logging before tests. mWifiConfigStore.enableVerboseLogging(true); } /** * Called after each test */ @After public void cleanup() { validateMockitoUsage(); } /** * Verify the contents of the config file with empty data. The data content should be the * same as {@link #EMPTY_FILE_CONTENT}. * * @throws Exception */ @Test public void testWriteWithEmptyData() throws Exception { // Perform force write to both share and user store file. mWifiConfigStore.switchUserStoreAndRead(mUserStore); mWifiConfigStore.write(true); assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG)); assertTrue(mSharedStore.isStoreWritten()); assertTrue(mUserStore.isStoreWritten()); assertTrue(Arrays.equals(EMPTY_FILE_CONTENT.getBytes(StandardCharsets.UTF_8), mSharedStore.getStoreBytes())); assertTrue(Arrays.equals(EMPTY_FILE_CONTENT.getBytes(StandardCharsets.UTF_8), mUserStore.getStoreBytes())); } /** * 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 { mWifiConfigStore.switchUserStoreAndRead(mUserStore); mWifiConfigStore.write(true); assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG)); assertTrue(mSharedStore.isStoreWritten()); assertTrue(mUserStore.isStoreWritten()); } /** * 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 { mWifiConfigStore.switchUserStoreAndRead(mUserStore); mWifiConfigStore.write(false); assertTrue(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG)); assertFalse(mSharedStore.isStoreWritten()); assertFalse(mUserStore.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()); } /** * 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(mStoreData); mStoreData.setShareData("abcds"); mStoreData.setUserData("asdfa"); // Perform buffered write for both user and share store file. mWifiConfigStore.switchUserStoreAndRead(mUserStore); 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. mStoreData.setUserData(TEST_USER_DATA); mStoreData.setShareData(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, mStoreData.getUserData()); assertEquals(TEST_SHARE_DATA, mStoreData.getShareData()); } /** * 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(mStoreData); // Read both share and user config store. mWifiConfigStore.switchUserStoreAndRead(mUserStore); // Verify no data is read. assertNull(mStoreData.getUserData()); assertNull(mStoreData.getShareData()); // Write share and user data. mStoreData.setUserData(TEST_USER_DATA); mStoreData.setShareData(TEST_SHARE_DATA); mWifiConfigStore.write(true); // Read and verify the data content in the data container. mWifiConfigStore.read(); assertEquals(TEST_USER_DATA, mStoreData.getUserData()); assertEquals(TEST_SHARE_DATA, mStoreData.getShareData()); } /** * Tests the read API behaviour when there is no 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 testReadWithNoStoreFile() throws Exception { // Reading the mock store without a write should simulate the file not found case because // |readRawData| would return null. mWifiConfigStore.registerStoreData(mStoreData); assertFalse(mWifiConfigStore.areStoresPresent()); mWifiConfigStore.read(); // Empty data. assertNull(mStoreData.getUserData()); assertNull(mStoreData.getShareData()); } /** * 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(mStoreData); mStoreData.setUserData(TEST_USER_DATA); mStoreData.setShareData(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, mStoreData.getShareData()); assertNull(mStoreData.getUserData()); } /** * 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(mStoreData); // Perform force write with empty data content to both user and share store file. mWifiConfigStore.switchUserStoreAndRead(mUserStore); mWifiConfigStore.write(true); // Setup data container with some value. mStoreData.setUserData(TEST_USER_DATA); mStoreData.setShareData(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(mStoreData.getShareData()); assertNull(mStoreData.getUserData()); } /** * 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 NetworkListStoreData(mContext); mWifiConfigStore.registerStoreData(networkList); WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(); openNetwork.creatorName = TEST_CREATOR_NAME; openNetwork.setIpConfiguration( WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy()); List userConfigs = new ArrayList<>(); userConfigs.add(openNetwork); // Setup deleted ephemeral SSID list. DeletedEphemeralSsidsStoreData deletedEphemeralSsids = new DeletedEphemeralSsidsStoreData(); mWifiConfigStore.registerStoreData(deletedEphemeralSsids); String testSsid = "Test SSID"; Set ssidList = new HashSet<>(); ssidList.add(testSsid); // 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, testSsid); byte[] xmlBytes = xmlString.getBytes(StandardCharsets.UTF_8); mUserStore.storeRawDataToWrite(xmlBytes); mWifiConfigStore.switchUserStoreAndRead(mUserStore); WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore( userConfigs, networkList.getUserConfigurations()); assertEquals(ssidList, deletedEphemeralSsids.getSsidList()); } /** * 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.switchUserStoreAndRead(mUserStore); // Setup network list store data. NetworkListStoreData networkList = new NetworkListStoreData(mContext); mWifiConfigStore.registerStoreData(networkList); WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(); openNetwork.creatorName = TEST_CREATOR_NAME; openNetwork.setIpConfiguration( WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy()); List userConfigs = new ArrayList<>(); userConfigs.add(openNetwork); networkList.setUserConfigurations(userConfigs); // Setup deleted ephemeral SSID list store data. DeletedEphemeralSsidsStoreData deletedEphemeralSsids = new DeletedEphemeralSsidsStoreData(); mWifiConfigStore.registerStoreData(deletedEphemeralSsids); String testSsid = "Test SSID"; Set ssidList = new HashSet<>(); ssidList.add(testSsid); deletedEphemeralSsids.setSsidList(ssidList); // 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, testSsid); byte[] xmlBytes = xmlString.getBytes(StandardCharsets.UTF_8); mWifiConfigStore.write(true); assertEquals(xmlBytes.length, mUserStore.getStoreBytes().length); // Verify the user store content. assertTrue(Arrays.equals(xmlBytes, mUserStore.getStoreBytes())); } /** * 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.switchUserStoreAndRead(mUserStore); } /** * 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(); } /** * 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; public MockStoreFile() { super(new File("MockStoreFile")); } @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() { 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 WifiConfigStore.StoreData { private static final String XML_TAG_TEST_HEADER = "TestHeader"; private static final String XML_TAG_TEST_DATA = "TestData"; private String mShareData; private String mUserData; MockStoreData() {} @Override public void serializeData(XmlSerializer out, boolean shared) throws XmlPullParserException, IOException { if (shared) { XmlUtil.writeNextValue(out, XML_TAG_TEST_DATA, mShareData); } else { XmlUtil.writeNextValue(out, XML_TAG_TEST_DATA, mUserData); } } @Override public void deserializeData(XmlPullParser in, int outerTagDepth, boolean shared) throws XmlPullParserException, IOException { if (shared) { mShareData = (String) XmlUtil.readNextValueWithName(in, XML_TAG_TEST_DATA); } else { mUserData = (String) XmlUtil.readNextValueWithName(in, XML_TAG_TEST_DATA); } } @Override public void resetData(boolean shared) { if (shared) { mShareData = null; } else { mUserData = null; } } @Override public String getName() { return XML_TAG_TEST_HEADER; } @Override public boolean supportShareData() { return true; } public String getShareData() { return mShareData; } public void setShareData(String shareData) { mShareData = shareData; } public String getUserData() { return mUserData; } public void setUserData(String userData) { mUserData = userData; } } }