Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4a172a4f authored by Roshan Pius's avatar Roshan Pius Committed by Automerger Merge Worker
Browse files

Merge "WifiMigration: Add helper to perform softap.conf to XML conversion"...

Merge "WifiMigration: Add helper to perform softap.conf to XML conversion" into rvc-dev am: fab0c25b am: 9e8edd61 am: 861ad297

Change-Id: Id3327b3ac905754874f6b0053ab9c675cb2666bb
parents 025f0a42 861ad297
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ filegroup {
        // TODO(b/146011398) package android.net.wifi is now split amongst 2 jars: framework.jar and
        // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
        // to a separate package.
        "java/android/net/wifi/SoftApConfToXmlMigrationUtil.java",
        "java/android/net/wifi/WifiNetworkScoreCache.java",
        "java/android/net/wifi/WifiMigration.java",
        "java/android/net/wifi/nl80211/*.java",
+284 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 android.net.wifi;

import static android.os.Environment.getDataMiscDirectory;

import android.annotation.Nullable;
import android.net.MacAddress;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;

import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

/**
 * Utility class to convert the legacy softap.conf file format to the new XML format.
 * Note:
 * <li>This should be modified by the OEM if they want to migrate configuration for existing
 * devices for new softap features supported by AOSP in Android 11.
 * For ex: client allowlist/blocklist feature was already supported by some OEM's before Android 10
 * while AOSP only supported it in Android 11. </li>
 * <li>Most of this class was copied over from WifiApConfigStore class in Android 10 and
 * SoftApStoreData class in Android 11</li>
 * @hide
 */
public final class SoftApConfToXmlMigrationUtil {
    private static final String TAG = "SoftApConfToXmlMigrationUtil";

    /**
     * Directory to read the wifi config store files from under.
     */
    private static final String LEGACY_WIFI_STORE_DIRECTORY_NAME = "wifi";
    /**
     * The legacy Softap config file which contained key/value pairs.
     */
    private static final String LEGACY_AP_CONFIG_FILE = "softap.conf";

    /**
     * Pre-apex wifi shared folder.
     */
    private static File getLegacyWifiSharedDirectory() {
        return new File(getDataMiscDirectory(), LEGACY_WIFI_STORE_DIRECTORY_NAME);
    }

    /* @hide constants copied from WifiConfiguration */
    /**
     * 2GHz band.
     */
    private static final int WIFICONFIG_AP_BAND_2GHZ = 0;
    /**
     * 5GHz band.
     */
    private static final int WIFICONFIG_AP_BAND_5GHZ = 1;
    /**
     * Device is allowed to choose the optimal band (2Ghz or 5Ghz) based on device capability,
     * operating country code and current radio conditions.
     */
    private static final int WIFICONFIG_AP_BAND_ANY = -1;
    /**
     * Convert band from WifiConfiguration into SoftApConfiguration
     *
     * @param wifiConfigBand band encoded as WIFICONFIG_AP_BAND_xxxx
     * @return band as encoded as SoftApConfiguration.BAND_xxx
     */
    @VisibleForTesting
    public static int convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand) {
        switch (wifiConfigBand) {
            case WIFICONFIG_AP_BAND_2GHZ:
                return SoftApConfiguration.BAND_2GHZ;
            case WIFICONFIG_AP_BAND_5GHZ:
                return SoftApConfiguration.BAND_5GHZ;
            case WIFICONFIG_AP_BAND_ANY:
                return SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ;
            default:
                return SoftApConfiguration.BAND_2GHZ;
        }
    }

    /**
     * Load AP configuration from legacy persistent storage.
     * Note: This is deprecated and only used for migrating data once on reboot.
     */
    private static SoftApConfiguration loadFromLegacyFile(InputStream fis) {
        SoftApConfiguration config = null;
        DataInputStream in = null;
        try {
            SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
            in = new DataInputStream(new BufferedInputStream(fis));

            int version = in.readInt();
            if (version < 1 || version > 3) {
                Log.e(TAG, "Bad version on hotspot configuration file");
                return null;
            }
            configBuilder.setSsid(in.readUTF());

            if (version >= 2) {
                int band = in.readInt();
                int channel = in.readInt();
                if (channel == 0) {
                    configBuilder.setBand(
                            convertWifiConfigBandToSoftApConfigBand(band));
                } else {
                    configBuilder.setChannel(channel,
                            convertWifiConfigBandToSoftApConfigBand(band));
                }
            }
            if (version >= 3) {
                configBuilder.setHiddenSsid(in.readBoolean());
            }
            int authType = in.readInt();
            if (authType == WifiConfiguration.KeyMgmt.WPA2_PSK) {
                configBuilder.setPassphrase(in.readUTF(),
                        SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
            }
            config = configBuilder.build();
        } catch (IOException e) {
            Log.e(TAG, "Error reading hotspot configuration ",  e);
            config = null;
        } catch (IllegalArgumentException ie) {
            Log.e(TAG, "Invalid hotspot configuration ", ie);
            config = null;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    Log.e(TAG, "Error closing hotspot configuration during read", e);
                }
            }
        }
        // NOTE: OEM's should add their customized parsing code here.
        return config;
    }

    // This is the version that Android 11 released with.
    private static final int CONFIG_STORE_DATA_VERSION = 3;

    private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData";
    private static final String XML_TAG_VERSION = "Version";
    private static final String XML_TAG_SECTION_HEADER_SOFTAP = "SoftAp";
    private static final String XML_TAG_SSID = "SSID";
    private static final String XML_TAG_BSSID = "Bssid";
    private static final String XML_TAG_CHANNEL = "Channel";
    private static final String XML_TAG_HIDDEN_SSID = "HiddenSSID";
    private static final String XML_TAG_SECURITY_TYPE = "SecurityType";
    private static final String XML_TAG_AP_BAND = "ApBand";
    private static final String XML_TAG_PASSPHRASE = "Passphrase";
    private static final String XML_TAG_MAX_NUMBER_OF_CLIENTS = "MaxNumberOfClients";
    private static final String XML_TAG_AUTO_SHUTDOWN_ENABLED = "AutoShutdownEnabled";
    private static final String XML_TAG_SHUTDOWN_TIMEOUT_MILLIS = "ShutdownTimeoutMillis";
    private static final String XML_TAG_CLIENT_CONTROL_BY_USER = "ClientControlByUser";
    private static final String XML_TAG_BLOCKED_CLIENT_LIST = "BlockedClientList";
    private static final String XML_TAG_ALLOWED_CLIENT_LIST = "AllowedClientList";
    public static final String XML_TAG_CLIENT_MACADDRESS = "ClientMacAddress";

    private static byte[] convertConfToXml(SoftApConfiguration softApConf) {
        try {
            final XmlSerializer out = new FastXmlSerializer();
            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            out.setOutput(outputStream, StandardCharsets.UTF_8.name());

            // Header for the XML file.
            out.startDocument(null, true);
            out.startTag(null, XML_TAG_DOCUMENT_HEADER);
            XmlUtils.writeValueXml(CONFIG_STORE_DATA_VERSION, XML_TAG_VERSION, out);
            out.startTag(null, XML_TAG_SECTION_HEADER_SOFTAP);

            // SoftAp conf
            XmlUtils.writeValueXml(softApConf.getSsid(), XML_TAG_SSID, out);
            if (softApConf.getBssid() != null) {
                XmlUtils.writeValueXml(softApConf.getBssid().toString(), XML_TAG_BSSID, out);
            }
            XmlUtils.writeValueXml(softApConf.getBand(), XML_TAG_AP_BAND, out);
            XmlUtils.writeValueXml(softApConf.getChannel(), XML_TAG_CHANNEL, out);
            XmlUtils.writeValueXml(softApConf.isHiddenSsid(), XML_TAG_HIDDEN_SSID, out);
            XmlUtils.writeValueXml(softApConf.getSecurityType(), XML_TAG_SECURITY_TYPE, out);
            if (softApConf.getSecurityType() != SoftApConfiguration.SECURITY_TYPE_OPEN) {
                XmlUtils.writeValueXml(softApConf.getPassphrase(), XML_TAG_PASSPHRASE, out);
            }
            XmlUtils.writeValueXml(softApConf.getMaxNumberOfClients(),
                    XML_TAG_MAX_NUMBER_OF_CLIENTS, out);
            XmlUtils.writeValueXml(softApConf.isClientControlByUserEnabled(),
                    XML_TAG_CLIENT_CONTROL_BY_USER, out);
            XmlUtils.writeValueXml(softApConf.isAutoShutdownEnabled(),
                    XML_TAG_AUTO_SHUTDOWN_ENABLED, out);
            XmlUtils.writeValueXml(softApConf.getShutdownTimeoutMillis(),
                    XML_TAG_SHUTDOWN_TIMEOUT_MILLIS, out);
            out.startTag(null, XML_TAG_BLOCKED_CLIENT_LIST);
            for (MacAddress mac: softApConf.getBlockedClientList()) {
                XmlUtils.writeValueXml(mac.toString(), XML_TAG_CLIENT_MACADDRESS, out);
            }
            out.endTag(null, XML_TAG_BLOCKED_CLIENT_LIST);
            out.startTag(null, XML_TAG_ALLOWED_CLIENT_LIST);
            for (MacAddress mac: softApConf.getAllowedClientList()) {
                XmlUtils.writeValueXml(mac.toString(), XML_TAG_CLIENT_MACADDRESS, out);
            }
            out.endTag(null, XML_TAG_ALLOWED_CLIENT_LIST);

            // Footer for the XML file.
            out.endTag(null, XML_TAG_SECTION_HEADER_SOFTAP);
            out.endTag(null, XML_TAG_DOCUMENT_HEADER);
            out.endDocument();

            return outputStream.toByteArray();
        } catch (IOException | XmlPullParserException e) {
            Log.e(TAG, "Failed to convert softap conf to XML", e);
            return null;
        }
    }

    private SoftApConfToXmlMigrationUtil() { }

    /**
     * Read the legacy /data/misc/wifi/softap.conf file format and convert to the new XML
     * format understood by WifiConfigStore.
     * Note: Used for unit testing.
     */
    @VisibleForTesting
    @Nullable
    public static InputStream convert(InputStream fis) {
        SoftApConfiguration softApConf = loadFromLegacyFile(fis);
        if (softApConf == null) return null;

        byte[] xmlBytes = convertConfToXml(softApConf);
        if (xmlBytes == null) return null;

        return new ByteArrayInputStream(xmlBytes);
    }

    /**
     * Read the legacy /data/misc/wifi/softap.conf file format and convert to the new XML
     * format understood by WifiConfigStore.
     */
    @Nullable
    public static InputStream convert() {
        File file = new File(getLegacyWifiSharedDirectory(), LEGACY_AP_CONFIG_FILE);
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            return null;
        }
        if (fis == null) return null;
        return convert(fis);
    }

    /**
     * Remove the legacy /data/misc/wifi/softap.conf file.
     */
    @Nullable
    public static void remove() {
        File file = new File(getLegacyWifiSharedDirectory(), LEGACY_AP_CONFIG_FILE);
        file.delete();
    }
}
+24 −3
Original line number Diff line number Diff line
@@ -95,7 +95,7 @@ public final class WifiMigration {
    private static final SparseArray<String> STORE_ID_TO_FILE_NAME =
            new SparseArray<String>() {{
                put(STORE_FILE_SHARED_GENERAL, "WifiConfigStore.xml");
                put(STORE_FILE_SHARED_SOFTAP, "softap.conf");
                put(STORE_FILE_SHARED_SOFTAP, "WifiConfigStoreSoftAp.xml");
                put(STORE_FILE_USER_GENERAL, "WifiConfigStore.xml");
                put(STORE_FILE_USER_NETWORK_SUGGESTIONS, "WifiConfigStoreNetworkSuggestions.xml");
            }};
@@ -176,6 +176,13 @@ public final class WifiMigration {
            // OEMs should do conversions necessary here before returning the stream.
            return getSharedAtomicFile(storeFileId).openRead();
        } catch (FileNotFoundException e) {
            // Special handling for softap.conf.
            // Note: OEM devices upgrading from Q -> R will only have the softap.conf file.
            // Test devices running previous R builds however may have already migrated to the
            // XML format. So, check for that above before falling back to check for legacy file.
            if (storeFileId == STORE_FILE_SHARED_SOFTAP) {
                return SoftApConfToXmlMigrationUtil.convert();
            }
            return null;
        }
    }
@@ -191,7 +198,18 @@ public final class WifiMigration {
        if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId !=  STORE_FILE_SHARED_SOFTAP) {
            throw new IllegalArgumentException("Invalid shared store file id");
        }
        getSharedAtomicFile(storeFileId).delete();
        AtomicFile file = getSharedAtomicFile(storeFileId);
        if (file.exists()) {
            file.delete();
            return;
        }
        // Special handling for softap.conf.
        // Note: OEM devices upgrading from Q -> R will only have the softap.conf file.
        // Test devices running previous R builds however may have already migrated to the
        // XML format. So, check for that above before falling back to check for legacy file.
        if (storeFileId == STORE_FILE_SHARED_SOFTAP) {
            SoftApConfToXmlMigrationUtil.remove();
        }
    }

    /**
@@ -258,7 +276,10 @@ public final class WifiMigration {
            throw new IllegalArgumentException("Invalid user store file id");
        }
        Objects.requireNonNull(userHandle);
        getUserAtomicFile(storeFileId, userHandle.getIdentifier()).delete();
        AtomicFile file = getUserAtomicFile(storeFileId, userHandle.getIdentifier());
        if (file.exists()) {
            file.delete();
        }
    }

    /**
+199 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 android.net.wifi;


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import androidx.test.filters.SmallTest;

import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Unit tests for {@link android.net.wifi.SoftApConfToXmlMigrationUtilTest}.
 */
@SmallTest
public class SoftApConfToXmlMigrationUtilTest {
    private static final String TEST_SSID = "SSID";
    private static final String TEST_PASSPHRASE = "TestPassphrase";
    private static final int TEST_CHANNEL = 0;
    private static final boolean TEST_HIDDEN = false;
    private static final int TEST_BAND = SoftApConfiguration.BAND_5GHZ;
    private static final int TEST_SECURITY = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK;

    private static final String TEST_EXPECTED_XML_STRING =
            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                    + "<WifiConfigStoreData>\n"
                    + "<int name=\"Version\" value=\"3\" />\n"
                    + "<SoftAp>\n"
                    + "<string name=\"SSID\">" + TEST_SSID + "</string>\n"
                    + "<int name=\"ApBand\" value=\"" + TEST_BAND + "\" />\n"
                    + "<int name=\"Channel\" value=\"" + TEST_CHANNEL + "\" />\n"
                    + "<boolean name=\"HiddenSSID\" value=\"" + TEST_HIDDEN + "\" />\n"
                    + "<int name=\"SecurityType\" value=\"" + TEST_SECURITY + "\" />\n"
                    + "<string name=\"Passphrase\">" + TEST_PASSPHRASE + "</string>\n"
                    + "<int name=\"MaxNumberOfClients\" value=\"0\" />\n"
                    + "<boolean name=\"ClientControlByUser\" value=\"false\" />\n"
                    + "<boolean name=\"AutoShutdownEnabled\" value=\"true\" />\n"
                    + "<long name=\"ShutdownTimeoutMillis\" value=\"0\" />\n"
                    + "<BlockedClientList />\n"
                    + "<AllowedClientList />\n"
                    + "</SoftAp>\n"
                    + "</WifiConfigStoreData>\n";

    private byte[] createLegacyApConfFile(WifiConfiguration config) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        DataOutputStream out = new DataOutputStream(outputStream);
        out.writeInt(3);
        out.writeUTF(config.SSID);
        out.writeInt(config.apBand);
        out.writeInt(config.apChannel);
        out.writeBoolean(config.hiddenSSID);
        int authType = config.getAuthType();
        out.writeInt(authType);
        if (authType != WifiConfiguration.KeyMgmt.NONE) {
            out.writeUTF(config.preSharedKey);
        }
        out.close();
        return outputStream.toByteArray();
    }

    /**
     * Generate a SoftApConfiguration based on the specified parameters.
     */
    private SoftApConfiguration setupApConfig(
            String ssid, String preSharedKey, int keyManagement, int band, int channel,
            boolean hiddenSSID) {
        SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
        configBuilder.setSsid(ssid);
        configBuilder.setPassphrase(preSharedKey, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
        if (channel == 0) {
            configBuilder.setBand(band);
        } else {
            configBuilder.setChannel(channel, band);
        }
        configBuilder.setHiddenSsid(hiddenSSID);
        return configBuilder.build();
    }

    /**
     * Generate a WifiConfiguration based on the specified parameters.
     */
    private WifiConfiguration setupWifiConfigurationApConfig(
            String ssid, String preSharedKey, int keyManagement, int band, int channel,
            boolean hiddenSSID) {
        WifiConfiguration config = new WifiConfiguration();
        config.SSID = ssid;
        config.preSharedKey = preSharedKey;
        config.allowedKeyManagement.set(keyManagement);
        config.apBand = band;
        config.apChannel = channel;
        config.hiddenSSID = hiddenSSID;
        return config;
    }

    /**
     * Asserts that the WifiConfigurations equal to SoftApConfiguration.
     * This only compares the elements saved
     * for softAp used.
     */
    public static void assertWifiConfigurationEqualSoftApConfiguration(
            WifiConfiguration backup, SoftApConfiguration restore) {
        assertEquals(backup.SSID, restore.getSsid());
        assertEquals(backup.BSSID, restore.getBssid());
        assertEquals(SoftApConfToXmlMigrationUtil.convertWifiConfigBandToSoftApConfigBand(
                backup.apBand),
                restore.getBand());
        assertEquals(backup.apChannel, restore.getChannel());
        assertEquals(backup.preSharedKey, restore.getPassphrase());
        if (backup.getAuthType() == WifiConfiguration.KeyMgmt.WPA2_PSK) {
            assertEquals(SoftApConfiguration.SECURITY_TYPE_WPA2_PSK, restore.getSecurityType());
        } else {
            assertEquals(SoftApConfiguration.SECURITY_TYPE_OPEN, restore.getSecurityType());
        }
        assertEquals(backup.hiddenSSID, restore.isHiddenSsid());
    }

    /**
     * Note: This is a copy of {@link AtomicFile#readFully()} modified to use the passed in
     * {@link InputStream} which was returned using {@link AtomicFile#openRead()}.
     */
    private static byte[] readFully(InputStream stream) throws IOException {
        try {
            int pos = 0;
            int avail = stream.available();
            byte[] data = new byte[avail];
            while (true) {
                int amt = stream.read(data, pos, data.length - pos);
                if (amt <= 0) {
                    return data;
                }
                pos += amt;
                avail = stream.available();
                if (avail > data.length - pos) {
                    byte[] newData = new byte[pos + avail];
                    System.arraycopy(data, 0, newData, 0, pos);
                    data = newData;
                }
            }
        } finally {
            stream.close();
        }
    }

    /**
     * Tests conversion from legacy .conf file to XML file format.
     */
    @Test
    public void testConversion() throws Exception {
        WifiConfiguration backupConfig = setupWifiConfigurationApConfig(
                TEST_SSID,    /* SSID */
                TEST_PASSPHRASE,       /* preshared key */
                WifiConfiguration.KeyMgmt.WPA2_PSK,   /* key management */
                1,                 /* AP band (5GHz) */
                TEST_CHANNEL,                /* AP channel */
                TEST_HIDDEN            /* Hidden SSID */);
        SoftApConfiguration expectedConfig = setupApConfig(
                TEST_SSID,           /* SSID */
                TEST_PASSPHRASE,              /* preshared key */
                SoftApConfiguration.SECURITY_TYPE_WPA2_PSK,   /* security type */
                SoftApConfiguration.BAND_5GHZ, /* AP band (5GHz) */
                TEST_CHANNEL,                       /* AP channel */
                TEST_HIDDEN            /* Hidden SSID */);

        assertWifiConfigurationEqualSoftApConfiguration(backupConfig, expectedConfig);

        byte[] confBytes = createLegacyApConfFile(backupConfig);
        assertNotNull(confBytes);

        InputStream xmlStream = SoftApConfToXmlMigrationUtil.convert(
                new ByteArrayInputStream(confBytes));

        byte[] xmlBytes = readFully(xmlStream);
        assertNotNull(xmlBytes);

        assertEquals(TEST_EXPECTED_XML_STRING, new String(xmlBytes));
    }

}