/*
 * 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.MockAnswerUtil;
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.net.wifi.WifiMigration;
import android.net.wifi.util.HexEncoding;
import android.os.UserHandle;
import android.os.test.TestLooper;

import androidx.test.filters.SmallTest;

import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.util.FastPrintWriter;
import com.android.server.wifi.WifiConfigStore.StoreData;
import com.android.server.wifi.WifiConfigStore.StoreFile;
import com.android.server.wifi.util.ArrayUtils;
import com.android.server.wifi.util.EncryptedData;
import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
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.mockito.MockitoSession;
import org.mockito.stubbing.Answer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;

/**
 * Unit tests for {@link com.android.server.wifi.WifiConfigStore}.
 */
@SmallTest
public class WifiConfigStoreTest extends WifiBaseTest {
    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 int TEST_SUB_ID = 2;

    private static final String TEST_DATA_XML_STRING_FORMAT =
            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                    + "<WifiConfigStoreData>\n"
                    + "<int name=\"Version\" value=\"3\" />\n"
                    + "<NetworkList>\n"
                    + "<Network>\n"
                    + "<WifiConfiguration>\n"
                    + "<string name=\"ConfigKey\">%s</string>\n"
                    + "<string name=\"SSID\">%s</string>\n"
                    + "<null name=\"PreSharedKey\" />\n"
                    + "<null name=\"WEPKeys\" />\n"
                    + "<int name=\"WEPTxKeyIndex\" value=\"0\" />\n"
                    + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
                    + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
                    + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">01</byte-array>\n"
                    + "<byte-array name=\"AllowedProtocols\" num=\"1\">03</byte-array>\n"
                    + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
                    + "<byte-array name=\"AllowedGroupMgmtCiphers\" num=\"0\"></byte-array>\n"
                    + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
                    + "<boolean name=\"Shared\" value=\"%s\" />\n"
                    + "<boolean name=\"AutoJoinEnabled\" value=\"true\" />\n"
                    + "<int name=\"Priority\" value=\"0\" />\n"
                    + "<int name=\"DeletionPriority\" value=\"0\" />\n"
                    + "<int name=\"NumRebootsSinceLastUse\" value=\"0\" />\n"
                    + "<boolean name=\"RepeaterEnabled\" value=\"false\" />\n"
                    + "<boolean name=\"EnableWifi7\" value=\"true\" />\n"
                    + "<SecurityParamsList>\n"
                    + "<SecurityParams>\n"
                    + "<int name=\"SecurityType\" value=\"0\" />\n"
                    + "<boolean name=\"IsEnabled\" value=\"true\" />\n"
                    + "<boolean name=\"SaeIsH2eOnlyMode\" value=\"false\" />\n"
                    + "<boolean name=\"SaeIsPkOnlyMode\" value=\"false\" />\n"
                    + "<boolean name=\"IsAddedByAutoUpgrade\" value=\"false\" />\n"
                    + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
                    + "</SecurityParams>\n"
                    + "<SecurityParams>\n"
                    + "<int name=\"SecurityType\" value=\"6\" />\n"
                    + "<boolean name=\"IsEnabled\" value=\"true\" />\n"
                    + "<boolean name=\"SaeIsH2eOnlyMode\" value=\"false\" />\n"
                    + "<boolean name=\"SaeIsPkOnlyMode\" value=\"false\" />\n"
                    + "<boolean name=\"IsAddedByAutoUpgrade\" value=\"true\" />\n"
                    + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
                    + "</SecurityParams>\n"
                    + "</SecurityParamsList>\n"
                    + "<boolean name=\"SendDhcpHostname\" value=\"true\" />\n"
                    + "<boolean name=\"Trusted\" value=\"true\" />\n"
                    + "<boolean name=\"IsRestricted\" value=\"false\" />\n"
                    + "<boolean name=\"OemPaid\" value=\"false\" />\n"
                    + "<boolean name=\"OemPrivate\" value=\"false\" />\n"
                    + "<boolean name=\"CarrierMerged\" value=\"false\" />\n"
                    + "<null name=\"BSSID\" />\n"
                    + "<int name=\"Status\" value=\"2\" />\n"
                    + "<null name=\"FQDN\" />\n"
                    + "<null name=\"ProviderFriendlyName\" />\n"
                    + "<null name=\"LinkedNetworksList\" />\n"
                    + "<null name=\"DefaultGwMacAddress\" />\n"
                    + "<boolean name=\"ValidatedInternetAccess\" value=\"false\" />\n"
                    + "<boolean name=\"NoInternetAccessExpected\" value=\"false\" />\n"
                    + "<boolean name=\"MeteredHint\" value=\"false\" />\n"
                    + "<int name=\"MeteredOverride\" value=\"0\" />\n"
                    + "<boolean name=\"UseExternalScores\" value=\"false\" />\n"
                    + "<int name=\"CreatorUid\" value=\"%d\" />\n"
                    + "<string name=\"CreatorName\">%s</string>\n"
                    + "<int name=\"LastUpdateUid\" value=\"-1\" />\n"
                    + "<null name=\"LastUpdateName\" />\n"
                    + "<int name=\"LastConnectUid\" value=\"0\" />\n"
                    + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
                    + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
                    + "<string name=\"RandomizedMacAddress\">%s</string>\n"
                    + "<int name=\"MacRandomizationSetting\" value=\"3\" />\n"
                    + "<int name=\"CarrierId\" value=\"-1\" />\n"
                    + "<boolean name=\"IsMostRecentlyConnected\" value=\"false\" />\n"
                    + "<int name=\"SubscriptionId\" value=\"%d\" />\n"
                    + "<DppPrivateEcKey>\n"
                    + "<byte-array name=\"EncryptedData\" num=\"0\"></byte-array>\n"
                    + "<byte-array name=\"IV\" num=\"0\"></byte-array>\n"
                    + "</DppPrivateEcKey>\n"
                    + "<DppConnector>\n"
                    + "<byte-array name=\"EncryptedData\" num=\"0\"></byte-array>\n"
                    + "<byte-array name=\"IV\" num=\"0\"></byte-array>\n"
                    + "</DppConnector>\n"
                    + "<DppCSignKey>\n"
                    + "<byte-array name=\"EncryptedData\" num=\"0\"></byte-array>\n"
                    + "<byte-array name=\"IV\" num=\"0\"></byte-array>\n"
                    + "</DppCSignKey>\n"
                    + "<DppNetAccessKey>\n"
                    + "<byte-array name=\"EncryptedData\" num=\"0\"></byte-array>\n"
                    + "<byte-array name=\"IV\" num=\"0\"></byte-array>\n"
                    + "</DppNetAccessKey>\n"
                    + "</WifiConfiguration>\n"
                    + "<NetworkStatus>\n"
                    + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
                    + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\n"
                    + "<null name=\"ConnectChoice\" />\n"
                    + "<int name=\"ConnectChoiceRssi\" value=\"0\" />\n"
                    + "<boolean name=\"HasEverConnected\" value=\"false\" />\n"
                    + "<boolean name=\"CaptivePortalNeverDetected\" value=\"true\" />\n"
                    + "<boolean name=\"HasEverValidatedInternetAccess\" value=\"false\" />\n"
                    + "</NetworkStatus>\n"
                    + "<IpConfiguration>\n"
                    + "<string name=\"IpAssignment\">DHCP</string>\n"
                    + "<string name=\"ProxySettings\">NONE</string>\n"
                    + "</IpConfiguration>\n"
                    + "</Network>\n"
                    + "</NetworkList>\n"
                    + "</WifiConfigStoreData>\n";

    private static final String TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE =
            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                    + "<WifiConfigStoreData>\n"
                    + "<int name=\"Version\" value=\"1\" />\n"
                    + "<%s/>n"
                    + "</WifiConfigStoreData>\n";
    private static final String TEST_DATA_XML_STRING_FORMAT_V1_WITH_TWO_DATA_SOURCE =
            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                    + "<WifiConfigStoreData>\n"
                    + "<int name=\"Version\" value=\"1\" />\n"
                    + "<%s/>n"
                    + "<%s/>n"
                    + "</WifiConfigStoreData>\n";
    private static final String TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE =
            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                    + "<WifiConfigStoreData>\n"
                    + "<int name=\"Version\" value=\"2\" />\n"
                    + "<Integrity>\n"
                    + "<byte-array name=\"EncryptedData\" num=\"48\">%s</byte-array>\n"
                    + "<byte-array name=\"IV\" num=\"12\">%s</byte-array>\n"
                    + "</Integrity>\n"
                    + "<%s />\n"
                    + "</WifiConfigStoreData>\n";
    private static final String TEST_DATA_XML_STRING_FORMAT_V3_WITH_ONE_DATA_SOURCE =
            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                    + "<WifiConfigStoreData>\n"
                    + "<int name=\"Version\" value=\"3\" />\n"
                    + "<%s />\n"
                    + "</WifiConfigStoreData>\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 WifiConfigStoreEncryptionUtil mEncryptionUtil;
    private MockStoreFile mSharedStore;
    private MockStoreFile mSharedSoftApStore;
    private MockStoreFile mUserStore;
    private MockStoreFile mUserNetworkSuggestionsStore;
    private List<StoreFile> mUserStores = new ArrayList<StoreFile>();
    private MockStoreData mSharedStoreData;
    private MockStoreData mUserStoreData;
    private MockitoSession mSession;
    private @Mock WifiCarrierInfoStoreManagerData.DataSource mDataSource;

    /**
     * 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(mEncryptionUtil.encrypt(any(byte[].class)))
                .thenReturn(new EncryptedData(new byte[0], new byte[0]));
        when(mEncryptionUtil.decrypt(any(EncryptedData.class)))
                .thenReturn(new byte[0]);
        mSharedStore = new MockStoreFile(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
        mSharedSoftApStore = new MockStoreFile(WifiConfigStore.STORE_FILE_SHARED_SOFTAP);
        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);

        mSession = ExtendedMockito.mockitoSession()
                .mockStatic(WifiMigration.class, withSettings().lenient())
                .startMocking();
        when(WifiMigration.convertAndRetrieveSharedConfigStoreFile(anyInt())).thenReturn(null);
        when(WifiMigration.convertAndRetrieveUserConfigStoreFile(anyInt(), any())).thenReturn(null);
    }

    /**
     * Setup the test environment.
     */
    @Before
    public void setUp() throws Exception {
        setupMocks();

        mWifiConfigStore = new WifiConfigStore(mClock,
                mWifiMetrics, Arrays.asList(mSharedStore, mSharedSoftApStore));
        // Enable verbose logging before tests.
        mWifiConfigStore.enableVerboseLogging(true);
    }

    /**
     * Called after each test
     */
    @After
    public void cleanup() {
        validateMockitoUsage();
        if (mSession != null) {
            mSession.finishMocking();
        }
    }

    /**
     * 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();

        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();

        assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
        assertTrue(mSharedStore.isStoreWritten());
        assertTrue(mUserStore.isStoreWritten());
        assertFalse(mUserNetworkSuggestionsStore.isStoreWritten());

        verify(mWifiMetrics).noteWifiConfigStoreWriteDuration(anyInt());
    }

    /**
     * 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();

        // 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);
        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(), anyInt(), any());
        verify(userStoreData, never()).resetData();
        verify(userStoreData, never()).deserializeData(any(), anyInt(), anyInt(), any());
    }

    /**
     * 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);
        mWifiConfigStore.read();

        // Ensure that we got the call to deserialize empty shared & user data.
        verify(userStoreData).resetData();
        verify(userStoreData).deserializeData(eq(null), anyInt(), anyInt(), any());
        verify(sharedStoreData).resetData();
        verify(sharedStoreData).deserializeData(eq(null), anyInt(), anyInt(), any());
    }

    /**
     * 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();
        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();

        // 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<WifiConfiguration> userConfigs = new ArrayList<>();
        openNetwork.subscriptionId = TEST_SUB_ID;
        userConfigs.add(openNetwork);

        // Setup user store XML bytes.
        String xmlString = String.format(TEST_DATA_XML_STRING_FORMAT,
                openNetwork.getKey().replaceAll("\"", "&quot;"),
                openNetwork.SSID.replaceAll("\"", "&quot;"),
                openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName,
                openNetwork.getRandomizedMacAddress(), openNetwork.subscriptionId);
        byte[] xmlBytes = xmlString.getBytes(StandardCharsets.UTF_8);
        mUserStore.storeRawDataToWrite(xmlBytes);

        mWifiConfigStore.switchUserStoresAndRead(mUserStores);
        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore(
                userConfigs, networkList.getConfigurations());
    }

    /**
     * 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.createOpenOweNetwork();
        openNetwork.creatorName = TEST_CREATOR_NAME;
        openNetwork.setIpConfiguration(
                WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
        openNetwork.setRandomizedMacAddress(TEST_RANDOMIZED_MAC);
        openNetwork.subscriptionId = TEST_SUB_ID;
        List<WifiConfiguration> userConfigs = new ArrayList<>();
        userConfigs.add(openNetwork);
        networkList.setConfigurations(userConfigs);

        // Setup expected XML bytes.
        String xmlString = String.format(TEST_DATA_XML_STRING_FORMAT,
                openNetwork.getKey().replaceAll("\"", "&quot;"),
                openNetwork.SSID.replaceAll("\"", "&quot;"),
                openNetwork.shared, openNetwork.creatorUid, openNetwork.creatorName,
                openNetwork.getRandomizedMacAddress(), openNetwork.subscriptionId);

        mWifiConfigStore.write();
        // 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.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(storeData1Name)));
        when(storeData2.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(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)
                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
        verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
        verify(storeData2).deserializeData(eq(null), anyInt(), anyInt(), any());
        reset(storeData1, storeData2);

        // Scenario 2: StoreData2 in user store file.
        when(storeData1.getName()).thenReturn(storeData1Name);
        when(storeData2.getName()).thenReturn(storeData2Name);
        when(storeData1.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(storeData1Name)));
        when(storeData2.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(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(), anyInt(), any());
        verify(storeData2)
                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
        verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
        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.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(storeData1Name)));
        when(storeData2.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(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)
                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
        verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
        verify(storeData2)
                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
        verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
        reset(storeData1, storeData2);

        // Scenario 4: StoreData1 & StoreData2 in shared store file.
        when(storeData1.getName()).thenReturn(storeData1Name);
        when(storeData2.getName()).thenReturn(storeData2Name);
        when(storeData1.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(storeData1Name)));
        when(storeData2.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(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)
                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
        verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
        verify(storeData2)
                .deserializeDataForSection(notNull(), anyInt(), anyInt(), any(), anyString());
        verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
        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();

        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(), any());
        verify(userStoreData).serializeData(any(), any());
        verify(userStoreNetworkSuggestionsData, never()).serializeData(any(), any());
    }

    /**
     * Verify that we gracefully skip unknown section when reading an user store file.
     */
    @Test
    public void testReadUserStoreContainedUnknownSection() throws Exception {
        String storeFileData =
                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                        + "<WifiConfigStoreData>\n"
                        + "<int name=\"Version\" value=\"1\" />\n"
                        + "<UnknownTag>\n"    // No StoreData registered to handle this tag.
                        + "</UnknownTag>\n"
                        + "</WifiConfigStoreData>\n";
        mUserStore.storeRawDataToWrite(storeFileData.getBytes(StandardCharsets.UTF_8));
        mWifiConfigStore.switchUserStoresAndRead(mUserStores);
    }

    /**
     * Verify that we gracefully skip unknown section when reading a shared store file.
     */
    @Test
    public void testReadShareStoreContainedUnknownSection() throws Exception {
        String storeFileData =
                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                        + "<WifiConfigStoreData>\n"
                        + "<int name=\"Version\" value=\"1\" />\n"
                        + "<UnknownTag>\n"    // No StoreData registered to handle this tag.
                        + "</UnknownTag>\n"
                        + "</WifiConfigStoreData>\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);
        when(sharedStoreData.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(TEST_SHARE_DATA)));
        StoreData userStoreData = mock(StoreData.class);
        when(userStoreData.getStoreFileId())
                .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
        when(userStoreData.getName()).thenReturn(TEST_USER_DATA);
        when(userStoreData.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(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)).deserializeDataForSection(
                any(XmlPullParser.class), anyInt(),
                eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any(), eq(TEST_SHARE_DATA));
        verify(userStoreData, times(1)).deserializeDataForSection(
                any(XmlPullParser.class), anyInt(),
                eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any(), eq(TEST_USER_DATA));
    }

    /**
     * Tests the read API behaviour to ensure that the integrity data is parsed from the file.
     */
    @Test
    public void testReadVersion2StoreFile() throws Exception {
        byte[] encryptedData = new byte[0];
        byte[] iv = new byte[0];
        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.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(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.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(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.
        mWifiConfigStore.read();
        verify(sharedStoreData, times(1))
                .deserializeDataForSection(any(XmlPullParser.class), anyInt(),
                        eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any(),
                        eq(TEST_SHARE_DATA));
        verify(userStoreData, times(1))
                .deserializeDataForSection(any(XmlPullParser.class), anyInt(),
                        eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any(),
                        eq(TEST_USER_DATA));
    }

    /**
     * Tests the complete migration path all the way from reading from the migration stream to
     * parsing the XML data and sending it to the appropriate registered data sources.
     */
    @Test
    public void testMigration() throws Exception {
        // Setup both shared & user store migrations.
        StoreFile sharedStoreFile1 = mock(StoreFile.class);
        when(sharedStoreFile1.getFileId())
                .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
        StoreFile sharedStoreFile2 = mock(StoreFile.class);
        when(sharedStoreFile2.getFileId())
                .thenReturn(WifiConfigStore.STORE_FILE_SHARED_SOFTAP);
        StoreFile userStoreFile1 = mock(StoreFile.class);
        when(userStoreFile1.getFileId())
                .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
        StoreFile userStoreFile2 = mock(StoreFile.class);
        when(userStoreFile2.getFileId())
                .thenReturn(WifiConfigStore.STORE_FILE_USER_NETWORK_SUGGESTIONS);
        mWifiConfigStore = new WifiConfigStore(mClock,
                mWifiMetrics, Arrays.asList(sharedStoreFile1, sharedStoreFile2));
        mWifiConfigStore.setUserStores(Arrays.asList(userStoreFile1, userStoreFile2));

        // 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.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(TEST_SHARE_DATA)));
        StoreData userStoreData = mock(StoreData.class);
        when(userStoreData.getStoreFileId())
                .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
        when(userStoreData.getName()).thenReturn(TEST_USER_DATA);
        when(userStoreData.getSectionsToParse())
                .thenReturn(new HashSet<>(Collections.singleton(TEST_USER_DATA)));
        mWifiConfigStore.registerStoreData(sharedStoreData);
        mWifiConfigStore.registerStoreData(userStoreData);

        // Migration data
        InputStream sharedStream1 = mock(InputStream.class);
        InputStream sharedStream2 = mock(InputStream.class);
        InputStream userStream1 = mock(InputStream.class);
        InputStream userStream2 = mock(InputStream.class);
        when(WifiMigration.convertAndRetrieveSharedConfigStoreFile(
                WifiMigration.STORE_FILE_SHARED_GENERAL))
                .thenReturn(sharedStream1);
        when(WifiMigration.convertAndRetrieveSharedConfigStoreFile(
                WifiMigration.STORE_FILE_SHARED_SOFTAP))
                .thenReturn(sharedStream2);
        when(WifiMigration.convertAndRetrieveUserConfigStoreFile(
                eq(WifiMigration.STORE_FILE_USER_GENERAL), any()))
                .thenReturn(userStream1);
        when(WifiMigration.convertAndRetrieveUserConfigStoreFile(
                eq(WifiMigration.STORE_FILE_USER_NETWORK_SUGGESTIONS), any()))
                .thenReturn(userStream2);

        byte[] sharedStoreXmlBytes =
                String.format(TEST_DATA_XML_STRING_FORMAT_V3_WITH_ONE_DATA_SOURCE,
                        TEST_SHARE_DATA).getBytes();
        byte[] userStoreXmlBytes =
                String.format(TEST_DATA_XML_STRING_FORMAT_V3_WITH_ONE_DATA_SOURCE,
                        TEST_USER_DATA).getBytes();
        when(sharedStream1.available())
                .thenReturn(sharedStoreXmlBytes.length) // first time return file contents, then 0.
                .thenReturn(0);
        when(sharedStream2.available())
                .thenReturn(sharedStoreXmlBytes.length) // first time return file contents, then 0.
                .thenReturn(0);
        when(userStream1.available())
                .thenReturn(userStoreXmlBytes.length) // first time return file contents, then 0.
                .thenReturn(0);
        when(userStream2.available())
                .thenReturn(userStoreXmlBytes.length) // first time return file contents, then 0.
                .thenReturn(0);
        Answer sharedStreamReadAnswer = new MockAnswerUtil.AnswerWithArguments() {
            public int answer(byte[] b, int off, int len) {
                System.arraycopy(sharedStoreXmlBytes, 0, b, 0, sharedStoreXmlBytes.length);
                return sharedStoreXmlBytes.length;
            }
        };
        Answer userStreamReadAnswer = new MockAnswerUtil.AnswerWithArguments() {
            public int answer(byte[] b, int off, int len) {
                System.arraycopy(userStoreXmlBytes, 0, b, 0, userStoreXmlBytes.length);
                return userStoreXmlBytes.length;
            }
        };
        when(sharedStream1.read(any(byte[].class), anyInt(), anyInt()))
                .thenAnswer(sharedStreamReadAnswer) // first time return file contents, then 0.
                .thenReturn(0);
        when(sharedStream2.read(any(byte[].class), anyInt(), anyInt()))
                .thenAnswer(sharedStreamReadAnswer) // first time return file contents, then 0.
                .thenReturn(0);
        when(userStream1.read(any(byte[].class), anyInt(), anyInt()))
                .thenAnswer(userStreamReadAnswer) // first time return file contents, then 0.
                .thenReturn(0);
        when(userStream2.read(any(byte[].class), anyInt(), anyInt()))
                .thenAnswer(userStreamReadAnswer) // first time return file contents, then 0.
                .thenReturn(0);

        // Trigger read.
        mWifiConfigStore.read();

        // Verify that we read the data out of all the migration streams & we didn't read
        // from the files on disk.
        verify(sharedStream1, times(2)).available();
        verify(sharedStream1, times(2)).read(any(), anyInt(), anyInt());
        verify(sharedStream2, times(2)).available();
        verify(sharedStream2, times(2)).read(any(), anyInt(), anyInt());
        verify(userStream1, times(2)).available();
        verify(userStream1, times(2)).read(any(), anyInt(), anyInt());
        verify(userStream2, times(2)).available();
        verify(userStream2, times(2)).read(any(), anyInt(), anyInt());

        // Verify that we correctly deserialized the data and sent it to the corresponding sources.
        verify(sharedStoreData, times(1))
                .deserializeDataForSection(any(XmlPullParser.class), anyInt(),
                        eq(WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION), any(),
                        eq(TEST_SHARE_DATA));
        verify(userStoreData, times(1))
                .deserializeDataForSection(any(XmlPullParser.class), anyInt(),
                        eq(WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION), any(),
                        eq(TEST_USER_DATA));

        // Verify we did not read from the real store files.
        verify(sharedStoreFile1, never()).readRawData();
        verify(sharedStoreFile2, never()).readRawData();
        verify(userStoreFile1, never()).readRawData();
        verify(userStoreFile2, never()).readRawData();
    }

    /**
     * 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, UserHandle.ALL, mEncryptionUtil);
        }

        @Override
        public byte[] readRawData() {
            return mStoreBytes;
        }

        @Override
        public void storeRawDataToWrite(byte[] data) {
            mStoreBytes = data;
            mStoreWritten = false;
        }

        @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, WifiConfigStoreEncryptionUtil encryptionUtil)
                throws XmlPullParserException, IOException {
            XmlUtil.writeNextValue(out, XML_TAG_TEST_DATA, mData);
        }

        @Override
        public void deserializeData(XmlPullParser in, int outerTagDepth, int version,
                WifiConfigStoreEncryptionUtil encryptionUtil)
                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;
        }
    }

    /**
     * Verify dump will not crash when no UserStores set.
     */
    @Test
    public void testDump() {
        final FileDescriptor fd = new FileDescriptor();
        final FileOutputStream fout = new FileOutputStream(fd);
        final PrintWriter pw = new FastPrintWriter(fout);
        mWifiConfigStore.dump(fd, pw, new String[0]);

        mWifiConfigStore.setUserStores(mUserStores);
        mWifiConfigStore.dump(fd, pw, new String[0]);
    }
}
