diff options
| -rw-r--r-- | wifi/Android.bp | 1 | ||||
| -rwxr-xr-x | wifi/java/android/net/wifi/SoftApConfToXmlMigrationUtil.java | 284 | ||||
| -rwxr-xr-x | wifi/java/android/net/wifi/WifiMigration.java | 27 | ||||
| -rw-r--r-- | wifi/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java | 199 |
4 files changed, 508 insertions, 3 deletions
diff --git a/wifi/Android.bp b/wifi/Android.bp index 70c274128266..fbafa07fbce7 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -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", diff --git a/wifi/java/android/net/wifi/SoftApConfToXmlMigrationUtil.java b/wifi/java/android/net/wifi/SoftApConfToXmlMigrationUtil.java new file mode 100755 index 000000000000..c5472ce34478 --- /dev/null +++ b/wifi/java/android/net/wifi/SoftApConfToXmlMigrationUtil.java @@ -0,0 +1,284 @@ +/* + * 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(); + } +} diff --git a/wifi/java/android/net/wifi/WifiMigration.java b/wifi/java/android/net/wifi/WifiMigration.java index f2a1aec550b1..666d72d32ac7 100755 --- a/wifi/java/android/net/wifi/WifiMigration.java +++ b/wifi/java/android/net/wifi/WifiMigration.java @@ -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(); + } } /** diff --git a/wifi/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java b/wifi/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java new file mode 100644 index 000000000000..f49f387cbc6b --- /dev/null +++ b/wifi/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java @@ -0,0 +1,199 @@ +/* + * 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)); + } + +} |