/*
* 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.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.InetAddresses;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
import android.net.LinkAddress;
import android.net.MacAddress;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.StaticIpConfiguration;
import android.net.Uri;
import android.net.wifi.SecurityParams;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiEnterpriseConfig;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.SparseIntArray;
import com.android.modules.utils.build.SdkLevel;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
/**
* Utils for manipulating XML data. This is essentially a wrapper over XmlUtils provided by core.
* The utility provides methods to write/parse section headers and write/parse values.
* This utility is designed for formatting the XML into the following format:
*
*
*
*
* ...
*
*
*
* ...
*
*
*
*
* Note: These utility methods are meant to be used for:
* 1. Backup/restore wifi network data to/from cloud.
* 2. Persisting wifi network data to/from disk.
*/
public class XmlUtil {
private static final String TAG = "WifiXmlUtil";
/**
* Ensure that the XML stream is at a start tag or the end of document.
*
* @throws XmlPullParserException if parsing errors occur.
*/
private static void gotoStartTag(XmlPullParser in)
throws XmlPullParserException, IOException {
int type = in.getEventType();
while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
type = in.next();
}
}
/**
* Ensure that the XML stream is at an end tag or the end of document.
*
* @throws XmlPullParserException if parsing errors occur.
*/
private static void gotoEndTag(XmlPullParser in)
throws XmlPullParserException, IOException {
int type = in.getEventType();
while (type != XmlPullParser.END_TAG && type != XmlPullParser.END_DOCUMENT) {
type = in.next();
}
}
/**
* Start processing the XML stream at the document header.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param headerName expected name for the start tag.
* @throws XmlPullParserException if parsing errors occur.
*/
public static void gotoDocumentStart(XmlPullParser in, String headerName)
throws XmlPullParserException, IOException {
XmlUtilHelper.beginDocument(in, headerName);
}
/**
* Move the XML stream to the next section header or indicate if there are no more sections.
* The provided outerDepth is used to find sub sections within that depth.
*
* Use this to move across sections if the ordering of sections are variable. The returned name
* can be used to decide what section is next.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param headerName An array of one string, used to return the name of the next section.
* @param outerDepth Find section within this depth.
* @return {@code true} if a next section is found, {@code false} if there are no more sections.
* @throws XmlPullParserException if parsing errors occur.
*/
public static boolean gotoNextSectionOrEnd(
XmlPullParser in, String[] headerName, int outerDepth)
throws XmlPullParserException, IOException {
if (XmlUtilHelper.nextElementWithin(in, outerDepth)) {
headerName[0] = in.getName();
return true;
}
return false;
}
/**
* Move the XML stream to the next section header or indicate if there are no more sections.
* If a section, exists ensure that the name matches the provided name.
* The provided outerDepth is used to find sub sections within that depth.
*
* Use this to move across repeated sections until the end.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param expectedName expected name for the section header.
* @param outerDepth Find section within this depth.
* @return {@code true} if a next section is found, {@code false} if there are no more sections.
* @throws XmlPullParserException if the section header name does not match |expectedName|,
* or if parsing errors occur.
*/
public static boolean gotoNextSectionWithNameOrEnd(
XmlPullParser in, String expectedName, int outerDepth)
throws XmlPullParserException, IOException {
String[] headerName = new String[1];
if (gotoNextSectionOrEnd(in, headerName, outerDepth)) {
if (headerName[0].equals(expectedName)) {
return true;
}
throw new XmlPullParserException(
"Next section name does not match expected name: " + expectedName);
}
return false;
}
/**
* Move the XML stream to the next section header and ensure that the name matches the provided
* name.
* The provided outerDepth is used to find sub sections within that depth.
*
* Use this to move across sections if the ordering of sections are fixed.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param expectedName expected name for the section header.
* @param outerDepth Find section within this depth.
* @throws XmlPullParserException if the section header name does not match |expectedName|,
* there are no more sections or if parsing errors occur.
*/
public static void gotoNextSectionWithName(
XmlPullParser in, String expectedName, int outerDepth)
throws XmlPullParserException, IOException {
if (!gotoNextSectionWithNameOrEnd(in, expectedName, outerDepth)) {
throw new XmlPullParserException("Section not found. Expected: " + expectedName);
}
}
/**
* Checks if the stream is at the end of a section of values. This moves the stream to next tag
* and checks if it finds an end tag at the specified depth.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param sectionDepth depth of the start tag of this section. Used to match the end tag.
* @return {@code true} if a end tag at the provided depth is found, {@code false} otherwise
* @throws XmlPullParserException if parsing errors occur.
*/
public static boolean isNextSectionEnd(XmlPullParser in, int sectionDepth)
throws XmlPullParserException, IOException {
return !XmlUtilHelper.nextElementWithin(in, sectionDepth);
}
/**
* Read the current value in the XML stream using core XmlUtils and stores the retrieved
* value name in the string provided. This method reads the value contained in current start
* tag.
* Note: Because there could be genuine null values being read from the XML, this method raises
* an exception to indicate errors.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param valueName An array of one string, used to return the name attribute
* of the value's tag.
* @return value retrieved from the XML stream.
* @throws XmlPullParserException if parsing errors occur.
*/
public static Object readCurrentValue(XmlPullParser in, String[] valueName)
throws XmlPullParserException, IOException {
Object value = XmlUtilHelper.readValueXml(in, valueName);
// XmlUtils.readValue does not always move the stream to the end of the tag. So, move
// it to the end tag before returning from here.
gotoEndTag(in);
return value;
}
/**
* Read the next value in the XML stream using core XmlUtils and ensure that it matches the
* provided name. This method moves the stream to the next start tag and reads the value
* contained in it.
* Note: Because there could be genuine null values being read from the XML, this method raises
* an exception to indicate errors.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @return value retrieved from the XML stream.
* @throws XmlPullParserException if the value read does not match |expectedName|,
* or if parsing errors occur.
*/
public static Object readNextValueWithName(XmlPullParser in, String expectedName)
throws XmlPullParserException, IOException {
String[] valueName = new String[1];
XmlUtilHelper.nextElement(in);
Object value = readCurrentValue(in, valueName);
if (valueName[0].equals(expectedName)) {
return value;
}
throw new XmlPullParserException(
"Value not found. Expected: " + expectedName + ", but got: " + valueName[0]);
}
/**
* Write the XML document start with the provided document header name.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param headerName name for the start tag.
*/
public static void writeDocumentStart(XmlSerializer out, String headerName)
throws IOException {
out.startDocument(null, true);
out.startTag(null, headerName);
}
/**
* Write the XML document end with the provided document header name.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param headerName name for the end tag.
*/
public static void writeDocumentEnd(XmlSerializer out, String headerName)
throws IOException {
out.endTag(null, headerName);
out.endDocument();
}
/**
* Write a section start header tag with the provided section name.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param headerName name for the start tag.
*/
public static void writeNextSectionStart(XmlSerializer out, String headerName)
throws IOException {
out.startTag(null, headerName);
}
/**
* Write a section end header tag with the provided section name.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param headerName name for the end tag.
*/
public static void writeNextSectionEnd(XmlSerializer out, String headerName)
throws IOException {
out.endTag(null, headerName);
}
/**
* Write the value with the provided name in the XML stream using core XmlUtils.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param name name of the value.
* @param value value to be written.
*/
public static void writeNextValue(XmlSerializer out, String name, Object value)
throws XmlPullParserException, IOException {
XmlUtilHelper.writeValueXml(value, name, out);
}
/**
* Utility class to serialize and deserialize {@link WifiConfiguration} object to XML &
* vice versa.
* This is used by both {@link com.android.server.wifi.WifiConfigStore} &
* {@link com.android.server.wifi.WifiBackupRestore} modules.
* The |writeConfigurationToXml| has 2 versions, one for backup and one for config store.
* There is only 1 version of |parseXmlToConfiguration| for both backup & config store.
* The parse method is written so that any element added/deleted in future revisions can
* be easily handled.
*/
public static class WifiConfigurationXmlUtil {
/**
* List of XML tags corresponding to WifiConfiguration object elements.
*/
public static final String XML_TAG_SSID = "SSID";
public static final String XML_TAG_BSSID = "BSSID";
public static final String XML_TAG_CONFIG_KEY = "ConfigKey";
public static final String XML_TAG_PRE_SHARED_KEY = "PreSharedKey";
public static final String XML_TAG_WEP_KEYS = "WEPKeys";
public static final String XML_TAG_WEP_TX_KEY_INDEX = "WEPTxKeyIndex";
public static final String XML_TAG_HIDDEN_SSID = "HiddenSSID";
public static final String XML_TAG_REQUIRE_PMF = "RequirePMF";
public static final String XML_TAG_ALLOWED_KEY_MGMT = "AllowedKeyMgmt";
public static final String XML_TAG_ALLOWED_PROTOCOLS = "AllowedProtocols";
public static final String XML_TAG_ALLOWED_AUTH_ALGOS = "AllowedAuthAlgos";
public static final String XML_TAG_ALLOWED_GROUP_CIPHERS = "AllowedGroupCiphers";
public static final String XML_TAG_ALLOWED_PAIRWISE_CIPHERS = "AllowedPairwiseCiphers";
public static final String XML_TAG_ALLOWED_GROUP_MGMT_CIPHERS = "AllowedGroupMgmtCiphers";
public static final String XML_TAG_ALLOWED_SUITE_B_CIPHERS = "AllowedSuiteBCiphers";
public static final String XML_TAG_SHARED = "Shared";
public static final String XML_TAG_STATUS = "Status";
public static final String XML_TAG_FQDN = "FQDN";
public static final String XML_TAG_PROVIDER_FRIENDLY_NAME = "ProviderFriendlyName";
public static final String XML_TAG_LINKED_NETWORKS_LIST = "LinkedNetworksList";
public static final String XML_TAG_DEFAULT_GW_MAC_ADDRESS = "DefaultGwMacAddress";
public static final String XML_TAG_VALIDATED_INTERNET_ACCESS = "ValidatedInternetAccess";
public static final String XML_TAG_NO_INTERNET_ACCESS_EXPECTED = "NoInternetAccessExpected";
public static final String XML_TAG_METERED_HINT = "MeteredHint";
public static final String XML_TAG_METERED_OVERRIDE = "MeteredOverride";
public static final String XML_TAG_USE_EXTERNAL_SCORES = "UseExternalScores";
public static final String XML_TAG_CREATOR_UID = "CreatorUid";
public static final String XML_TAG_CREATOR_NAME = "CreatorName";
public static final String XML_TAG_LAST_UPDATE_UID = "LastUpdateUid";
public static final String XML_TAG_LAST_UPDATE_NAME = "LastUpdateName";
public static final String XML_TAG_LAST_CONNECT_UID = "LastConnectUid";
public static final String XML_TAG_IS_LEGACY_PASSPOINT_CONFIG = "IsLegacyPasspointConfig";
public static final String XML_TAG_ROAMING_CONSORTIUM_OIS = "RoamingConsortiumOIs";
public static final String XML_TAG_RANDOMIZED_MAC_ADDRESS = "RandomizedMacAddress";
public static final String XML_TAG_MAC_RANDOMIZATION_SETTING = "MacRandomizationSetting";
public static final String XML_TAG_CARRIER_ID = "CarrierId";
public static final String XML_TAG_SUBSCRIPTION_ID = "SubscriptionId";
public static final String XML_TAG_IS_AUTO_JOIN = "AutoJoinEnabled";
public static final String XML_TAG_DELETION_PRIORITY = "DeletionPriority";
public static final String XML_TAG_NUM_REBOOTS_SINCE_LAST_USE = "NumRebootsSinceLastUse";
public static final String XML_TAG_IS_TRUSTED = "Trusted";
public static final String XML_TAG_IS_OEM_PAID = "OemPaid";
public static final String XML_TAG_IS_OEM_PRIVATE = "OemPrivate";
public static final String XML_TAG_IS_CARRIER_MERGED = "CarrierMerged";
public static final String XML_TAG_SECURITY_PARAMS_LIST = "SecurityParamsList";
public static final String XML_TAG_SECURITY_PARAMS = "SecurityParams";
public static final String XML_TAG_SECURITY_TYPE = "SecurityType";
public static final String XML_TAG_SAE_IS_H2E_ONLY_MODE = "SaeIsH2eOnlyMode";
public static final String XML_TAG_SAE_IS_PK_ONLY_MODE = "SaeIsPkOnlyMode";
public static final String XML_TAG_IS_ADDED_BY_AUTO_UPGRADE = "IsAddedByAutoUpgrade";
private static final String XML_TAG_IS_MOST_RECENTLY_CONNECTED = "IsMostRecentlyConnected";
/**
* Write WepKeys to the XML stream.
* WepKeys array is intialized in WifiConfiguration constructor, but all of the elements
* are set to null. User may chose to set any one of the key elements in WifiConfiguration.
* XmlUtils serialization doesn't handle this array of nulls well .
* So, write empty strings if some of the keys are not initialized and null if all of
* the elements are empty.
*/
private static void writeWepKeysToXml(XmlSerializer out, String[] wepKeys)
throws XmlPullParserException, IOException {
String[] wepKeysToWrite = new String[wepKeys.length];
boolean hasWepKey = false;
for (int i = 0; i < wepKeys.length; i++) {
if (wepKeys[i] == null) {
wepKeysToWrite[i] = new String();
} else {
wepKeysToWrite[i] = wepKeys[i];
hasWepKey = true;
}
}
if (hasWepKey) {
XmlUtil.writeNextValue(out, XML_TAG_WEP_KEYS, wepKeysToWrite);
} else {
XmlUtil.writeNextValue(out, XML_TAG_WEP_KEYS, null);
}
}
/**
* Write preshared key to the XML stream.
*
* If encryptionUtil is null or if encryption fails for some reason, the pre-shared
* key is stored in plaintext, else the encrypted psk is stored.
*/
private static void writePreSharedKeyToXml(
XmlSerializer out, String preSharedKey,
@Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
throws XmlPullParserException, IOException {
EncryptedData encryptedData = null;
if (encryptionUtil != null) {
if (preSharedKey != null) {
encryptedData = encryptionUtil.encrypt(preSharedKey.getBytes());
if (encryptedData == null) {
// We silently fail encryption failures!
Log.wtf(TAG, "Encryption of preSharedKey failed");
}
}
}
if (encryptedData != null) {
XmlUtil.writeNextSectionStart(out, XML_TAG_PRE_SHARED_KEY);
EncryptedDataXmlUtil.writeToXml(out, encryptedData);
XmlUtil.writeNextSectionEnd(out, XML_TAG_PRE_SHARED_KEY);
} else {
XmlUtil.writeNextValue(out, XML_TAG_PRE_SHARED_KEY, preSharedKey);
}
}
private static void writeSecurityParamsListToXml(
XmlSerializer out, WifiConfiguration configuration)
throws XmlPullParserException, IOException {
XmlUtil.writeNextSectionStart(out, XML_TAG_SECURITY_PARAMS_LIST);
for (SecurityParams params: configuration.getSecurityParamsList()) {
XmlUtil.writeNextSectionStart(out, XML_TAG_SECURITY_PARAMS);
XmlUtil.writeNextValue(
out, XML_TAG_SECURITY_TYPE,
params.getSecurityType());
XmlUtil.writeNextValue(
out, XML_TAG_SAE_IS_H2E_ONLY_MODE,
params.isSaeH2eOnlyMode());
XmlUtil.writeNextValue(
out, XML_TAG_SAE_IS_PK_ONLY_MODE,
params.isSaePkOnlyMode());
XmlUtil.writeNextValue(
out, XML_TAG_IS_ADDED_BY_AUTO_UPGRADE,
params.isAddedByAutoUpgrade());
XmlUtil.writeNextSectionEnd(out, XML_TAG_SECURITY_PARAMS);
}
XmlUtil.writeNextSectionEnd(out, XML_TAG_SECURITY_PARAMS_LIST);
}
/**
* Write the Configuration data elements that are common for backup & config store to the
* XML stream.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param configuration WifiConfiguration object to be serialized.
* @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. Backup/restore stores
* keys unencrypted.
*/
public static void writeCommonElementsToXml(
XmlSerializer out, WifiConfiguration configuration,
@Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
throws XmlPullParserException, IOException {
XmlUtil.writeNextValue(out, XML_TAG_CONFIG_KEY, configuration.getKey());
XmlUtil.writeNextValue(out, XML_TAG_SSID, configuration.SSID);
writePreSharedKeyToXml(out, configuration.preSharedKey, encryptionUtil);
writeWepKeysToXml(out, configuration.wepKeys);
XmlUtil.writeNextValue(out, XML_TAG_WEP_TX_KEY_INDEX, configuration.wepTxKeyIndex);
XmlUtil.writeNextValue(out, XML_TAG_HIDDEN_SSID, configuration.hiddenSSID);
XmlUtil.writeNextValue(out, XML_TAG_REQUIRE_PMF, configuration.requirePmf);
XmlUtil.writeNextValue(
out, XML_TAG_ALLOWED_KEY_MGMT,
configuration.allowedKeyManagement.toByteArray());
XmlUtil.writeNextValue(
out, XML_TAG_ALLOWED_PROTOCOLS,
configuration.allowedProtocols.toByteArray());
XmlUtil.writeNextValue(
out, XML_TAG_ALLOWED_AUTH_ALGOS,
configuration.allowedAuthAlgorithms.toByteArray());
XmlUtil.writeNextValue(
out, XML_TAG_ALLOWED_GROUP_CIPHERS,
configuration.allowedGroupCiphers.toByteArray());
XmlUtil.writeNextValue(
out, XML_TAG_ALLOWED_PAIRWISE_CIPHERS,
configuration.allowedPairwiseCiphers.toByteArray());
XmlUtil.writeNextValue(
out, XML_TAG_ALLOWED_GROUP_MGMT_CIPHERS,
configuration.allowedGroupManagementCiphers.toByteArray());
XmlUtil.writeNextValue(
out, XML_TAG_ALLOWED_SUITE_B_CIPHERS,
configuration.allowedSuiteBCiphers.toByteArray());
XmlUtil.writeNextValue(out, XML_TAG_SHARED, configuration.shared);
XmlUtil.writeNextValue(out, XML_TAG_IS_AUTO_JOIN, configuration.allowAutojoin);
XmlUtil.writeNextValue(
out, XML_TAG_DELETION_PRIORITY,
configuration.getDeletionPriority());
XmlUtil.writeNextValue(
out, XML_TAG_NUM_REBOOTS_SINCE_LAST_USE,
configuration.numRebootsSinceLastUse);
writeSecurityParamsListToXml(out, configuration);
}
/**
* Write the Configuration data elements for backup from the provided Configuration to the
* XML stream.
* Note: This is a subset of the elements serialized for config store.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param configuration WifiConfiguration object to be serialized.
*/
public static void writeToXmlForBackup(XmlSerializer out, WifiConfiguration configuration)
throws XmlPullParserException, IOException {
writeCommonElementsToXml(out, configuration, null);
XmlUtil.writeNextValue(out, XML_TAG_METERED_OVERRIDE, configuration.meteredOverride);
}
/**
* Write the Configuration data elements for config store from the provided Configuration
* to the XML stream.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param configuration WifiConfiguration object to be serialized.
* @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}.
*/
public static void writeToXmlForConfigStore(
XmlSerializer out, WifiConfiguration configuration,
@Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
throws XmlPullParserException, IOException {
writeCommonElementsToXml(out, configuration, encryptionUtil);
XmlUtil.writeNextValue(out, XML_TAG_IS_TRUSTED, configuration.trusted);
XmlUtil.writeNextValue(out, XML_TAG_IS_OEM_PAID, configuration.oemPaid);
XmlUtil.writeNextValue(out, XML_TAG_IS_OEM_PRIVATE, configuration.oemPrivate);
XmlUtil.writeNextValue(out, XML_TAG_IS_CARRIER_MERGED,
configuration.carrierMerged);
XmlUtil.writeNextValue(out, XML_TAG_BSSID, configuration.BSSID);
XmlUtil.writeNextValue(out, XML_TAG_STATUS, configuration.status);
XmlUtil.writeNextValue(out, XML_TAG_FQDN, configuration.FQDN);
XmlUtil.writeNextValue(
out, XML_TAG_PROVIDER_FRIENDLY_NAME, configuration.providerFriendlyName);
XmlUtil.writeNextValue(
out, XML_TAG_LINKED_NETWORKS_LIST, configuration.linkedConfigurations);
XmlUtil.writeNextValue(
out, XML_TAG_DEFAULT_GW_MAC_ADDRESS, configuration.defaultGwMacAddress);
XmlUtil.writeNextValue(
out, XML_TAG_VALIDATED_INTERNET_ACCESS, configuration.validatedInternetAccess);
XmlUtil.writeNextValue(
out, XML_TAG_NO_INTERNET_ACCESS_EXPECTED,
configuration.noInternetAccessExpected);
XmlUtil.writeNextValue(out, XML_TAG_METERED_HINT, configuration.meteredHint);
XmlUtil.writeNextValue(out, XML_TAG_METERED_OVERRIDE, configuration.meteredOverride);
XmlUtil.writeNextValue(
out, XML_TAG_USE_EXTERNAL_SCORES, configuration.useExternalScores);
XmlUtil.writeNextValue(out, XML_TAG_CREATOR_UID, configuration.creatorUid);
XmlUtil.writeNextValue(out, XML_TAG_CREATOR_NAME, configuration.creatorName);
XmlUtil.writeNextValue(out, XML_TAG_LAST_UPDATE_UID, configuration.lastUpdateUid);
XmlUtil.writeNextValue(out, XML_TAG_LAST_UPDATE_NAME, configuration.lastUpdateName);
XmlUtil.writeNextValue(out, XML_TAG_LAST_CONNECT_UID, configuration.lastConnectUid);
XmlUtil.writeNextValue(
out, XML_TAG_IS_LEGACY_PASSPOINT_CONFIG,
configuration.isLegacyPasspointConfig);
XmlUtil.writeNextValue(
out, XML_TAG_ROAMING_CONSORTIUM_OIS, configuration.roamingConsortiumIds);
XmlUtil.writeNextValue(out, XML_TAG_RANDOMIZED_MAC_ADDRESS,
configuration.getRandomizedMacAddress().toString());
XmlUtil.writeNextValue(out, XML_TAG_MAC_RANDOMIZATION_SETTING,
configuration.macRandomizationSetting);
XmlUtil.writeNextValue(out, XML_TAG_CARRIER_ID, configuration.carrierId);
XmlUtil.writeNextValue(out, XML_TAG_IS_MOST_RECENTLY_CONNECTED,
configuration.isMostRecentlyConnected);
XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_ID, configuration.subscriptionId);
}
/**
* Populate wepKeys array elements only if they were non-empty in the backup data.
*
* @throws XmlPullParserException if parsing errors occur.
*/
private static void populateWepKeysFromXmlValue(Object value, String[] wepKeys)
throws XmlPullParserException, IOException {
String[] wepKeysInData = (String[]) value;
if (wepKeysInData == null) {
return;
}
if (wepKeysInData.length != wepKeys.length) {
throw new XmlPullParserException(
"Invalid Wep Keys length: " + wepKeysInData.length);
}
for (int i = 0; i < wepKeys.length; i++) {
if (wepKeysInData[i].isEmpty()) {
wepKeys[i] = null;
} else {
wepKeys[i] = wepKeysInData[i];
}
}
}
private static SecurityParams parseSecurityParamsFromXml(
XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException {
SecurityParams params = null;
while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
String[] valueName = new String[1];
Object value = XmlUtil.readCurrentValue(in, valueName);
String tagName = valueName[0];
if (tagName == null) {
throw new XmlPullParserException("Missing value name");
}
switch (tagName) {
case WifiConfigurationXmlUtil.XML_TAG_SECURITY_TYPE:
params = SecurityParams.createSecurityParamsBySecurityType((int) value);
break;
case WifiConfigurationXmlUtil.XML_TAG_SAE_IS_H2E_ONLY_MODE:
if (null == params) {
throw new XmlPullParserException("Missing security type.");
}
params.enableSaeH2eOnlyMode((boolean) value);
break;
case WifiConfigurationXmlUtil.XML_TAG_SAE_IS_PK_ONLY_MODE:
if (null == params) {
throw new XmlPullParserException("Missing security type.");
}
params.enableSaePkOnlyMode((boolean) value);
break;
case WifiConfigurationXmlUtil.XML_TAG_IS_ADDED_BY_AUTO_UPGRADE:
if (null == params) {
throw new XmlPullParserException("Missing security type.");
}
params.setIsAddedByAutoUpgrade((boolean) value);
break;
}
}
return params;
}
private static void parseSecurityParamsListFromXml(
XmlPullParser in, int outerTagDepth,
WifiConfiguration configuration)
throws XmlPullParserException, IOException {
List paramsList = new ArrayList<>();
while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
switch (in.getName()) {
case WifiConfigurationXmlUtil.XML_TAG_SECURITY_PARAMS:
SecurityParams params = parseSecurityParamsFromXml(in, outerTagDepth + 1);
if (params != null) {
paramsList.add(params);
}
break;
}
}
if (!paramsList.isEmpty()) {
configuration.setSecurityParams(paramsList);
}
}
/**
* Parses the configuration data elements from the provided XML stream to a
* WifiConfiguration object.
* Note: This is used for parsing both backup data and config store data. Looping through
* the tags make it easy to add or remove elements in the future versions if needed.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param outerTagDepth depth of the outer tag in the XML document.
* @param shouldExpectEncryptedCredentials Whether to expect encrypted credentials or not.
* @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}.
* @param fromSuggestion Is this WifiConfiguration created from a WifiNetworkSuggestion.
* @return Pair if parsing is successful,
* null otherwise.
*/
public static Pair parseFromXml(
XmlPullParser in, int outerTagDepth, boolean shouldExpectEncryptedCredentials,
@Nullable WifiConfigStoreEncryptionUtil encryptionUtil, boolean fromSuggestion)
throws XmlPullParserException, IOException {
WifiConfiguration configuration = new WifiConfiguration();
String configKeyInData = null;
boolean macRandomizationSettingExists = false;
// Loop through and parse out all the elements from the stream within this section.
while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
if (in.getAttributeValue(null, "name") != null) {
// Value elements.
String[] valueName = new String[1];
Object value = XmlUtil.readCurrentValue(in, valueName);
if (valueName[0] == null) {
throw new XmlPullParserException("Missing value name");
}
switch (valueName[0]) {
case XML_TAG_CONFIG_KEY:
configKeyInData = (String) value;
break;
case XML_TAG_SSID:
configuration.SSID = (String) value;
break;
case XML_TAG_BSSID:
configuration.BSSID = (String) value;
break;
case XML_TAG_PRE_SHARED_KEY:
configuration.preSharedKey = (String) value;
break;
case XML_TAG_WEP_KEYS:
populateWepKeysFromXmlValue(value, configuration.wepKeys);
break;
case XML_TAG_WEP_TX_KEY_INDEX:
configuration.wepTxKeyIndex = (int) value;
break;
case XML_TAG_HIDDEN_SSID:
configuration.hiddenSSID = (boolean) value;
break;
case XML_TAG_REQUIRE_PMF:
configuration.requirePmf = (boolean) value;
break;
case XML_TAG_ALLOWED_KEY_MGMT:
byte[] allowedKeyMgmt = (byte[]) value;
configuration.allowedKeyManagement = BitSet.valueOf(allowedKeyMgmt);
break;
case XML_TAG_ALLOWED_PROTOCOLS:
byte[] allowedProtocols = (byte[]) value;
configuration.allowedProtocols = BitSet.valueOf(allowedProtocols);
break;
case XML_TAG_ALLOWED_AUTH_ALGOS:
byte[] allowedAuthAlgorithms = (byte[]) value;
configuration.allowedAuthAlgorithms = BitSet.valueOf(
allowedAuthAlgorithms);
break;
case XML_TAG_ALLOWED_GROUP_CIPHERS:
byte[] allowedGroupCiphers = (byte[]) value;
configuration.allowedGroupCiphers = BitSet.valueOf(allowedGroupCiphers);
break;
case XML_TAG_ALLOWED_PAIRWISE_CIPHERS:
byte[] allowedPairwiseCiphers = (byte[]) value;
configuration.allowedPairwiseCiphers =
BitSet.valueOf(allowedPairwiseCiphers);
break;
case XML_TAG_ALLOWED_GROUP_MGMT_CIPHERS:
byte[] allowedGroupMgmtCiphers = (byte[]) value;
configuration.allowedGroupManagementCiphers =
BitSet.valueOf(allowedGroupMgmtCiphers);
break;
case XML_TAG_ALLOWED_SUITE_B_CIPHERS:
byte[] allowedSuiteBCiphers = (byte[]) value;
configuration.allowedSuiteBCiphers =
BitSet.valueOf(allowedSuiteBCiphers);
break;
case XML_TAG_SHARED:
configuration.shared = (boolean) value;
break;
case XML_TAG_STATUS:
int status = (int) value;
// Any network which was CURRENT before reboot needs
// to be restored to ENABLED.
if (status == WifiConfiguration.Status.CURRENT) {
status = WifiConfiguration.Status.ENABLED;
}
configuration.status = status;
break;
case XML_TAG_FQDN:
configuration.FQDN = (String) value;
break;
case XML_TAG_PROVIDER_FRIENDLY_NAME:
configuration.providerFriendlyName = (String) value;
break;
case XML_TAG_LINKED_NETWORKS_LIST:
configuration.linkedConfigurations = (HashMap) value;
break;
case XML_TAG_DEFAULT_GW_MAC_ADDRESS:
configuration.defaultGwMacAddress = (String) value;
break;
case XML_TAG_VALIDATED_INTERNET_ACCESS:
configuration.validatedInternetAccess = (boolean) value;
break;
case XML_TAG_NO_INTERNET_ACCESS_EXPECTED:
configuration.noInternetAccessExpected = (boolean) value;
break;
case XML_TAG_METERED_HINT:
configuration.meteredHint = (boolean) value;
break;
case XML_TAG_METERED_OVERRIDE:
configuration.meteredOverride = (int) value;
break;
case XML_TAG_USE_EXTERNAL_SCORES:
configuration.useExternalScores = (boolean) value;
break;
case XML_TAG_CREATOR_UID:
configuration.creatorUid = (int) value;
break;
case XML_TAG_CREATOR_NAME:
configuration.creatorName = (String) value;
break;
case XML_TAG_LAST_UPDATE_UID:
configuration.lastUpdateUid = (int) value;
break;
case XML_TAG_LAST_UPDATE_NAME:
configuration.lastUpdateName = (String) value;
break;
case XML_TAG_LAST_CONNECT_UID:
configuration.lastConnectUid = (int) value;
break;
case XML_TAG_IS_LEGACY_PASSPOINT_CONFIG:
configuration.isLegacyPasspointConfig = (boolean) value;
break;
case XML_TAG_ROAMING_CONSORTIUM_OIS:
configuration.roamingConsortiumIds = (long[]) value;
break;
case XML_TAG_RANDOMIZED_MAC_ADDRESS:
configuration.setRandomizedMacAddress(
MacAddress.fromString((String) value));
break;
case XML_TAG_MAC_RANDOMIZATION_SETTING:
configuration.macRandomizationSetting = (int) value;
macRandomizationSettingExists = true;
break;
case XML_TAG_CARRIER_ID:
configuration.carrierId = (int) value;
break;
case XML_TAG_SUBSCRIPTION_ID:
configuration.subscriptionId = (int) value;
break;
case XML_TAG_IS_AUTO_JOIN:
configuration.allowAutojoin = (boolean) value;
break;
case XML_TAG_DELETION_PRIORITY:
configuration.setDeletionPriority((int) value);
break;
case XML_TAG_NUM_REBOOTS_SINCE_LAST_USE:
configuration.numRebootsSinceLastUse = (int) value;
break;
case XML_TAG_IS_TRUSTED:
configuration.trusted = (boolean) value;
break;
case XML_TAG_IS_OEM_PAID:
configuration.oemPaid = (boolean) value;
break;
case XML_TAG_IS_OEM_PRIVATE:
configuration.oemPrivate = (boolean) value;
break;
case XML_TAG_IS_MOST_RECENTLY_CONNECTED:
configuration.isMostRecentlyConnected = (boolean) value;
break;
case XML_TAG_IS_CARRIER_MERGED:
configuration.carrierMerged = (boolean) value;
default:
Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]);
break;
}
} else {
String tagName = in.getName();
if (tagName == null) {
throw new XmlPullParserException("Unexpected null tag found");
}
switch (tagName) {
case XML_TAG_PRE_SHARED_KEY:
if (!shouldExpectEncryptedCredentials || encryptionUtil == null) {
throw new XmlPullParserException(
"Encrypted preSharedKey section not expected");
}
EncryptedData encryptedData =
EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1);
byte[] preSharedKeyBytes = encryptionUtil.decrypt(encryptedData);
if (preSharedKeyBytes == null) {
Log.wtf(TAG, "Decryption of preSharedKey failed");
} else {
configuration.preSharedKey = new String(preSharedKeyBytes);
}
break;
case XML_TAG_SECURITY_PARAMS_LIST:
parseSecurityParamsListFromXml(in, outerTagDepth + 1, configuration);
break;
default:
Log.w(TAG, "Ignoring unknown tag found: " + tagName);
break;
}
}
}
if (!macRandomizationSettingExists) {
configuration.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
}
if (configuration.macRandomizationSetting
== WifiConfiguration.RANDOMIZATION_PERSISTENT && !fromSuggestion) {
configuration.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO;
}
configuration.convertLegacyFieldsToSecurityParamsIfNeeded();
return Pair.create(configKeyInData, configuration);
}
}
/**
* Utility class to serialize and deseriaize {@link IpConfiguration} object to XML & vice versa.
* This is used by both {@link com.android.server.wifi.WifiConfigStore} &
* {@link com.android.server.wifi.WifiBackupRestore} modules.
*/
public static class IpConfigurationXmlUtil {
/**
* List of XML tags corresponding to IpConfiguration object elements.
*/
public static final String XML_TAG_IP_ASSIGNMENT = "IpAssignment";
public static final String XML_TAG_LINK_ADDRESS = "LinkAddress";
public static final String XML_TAG_LINK_PREFIX_LENGTH = "LinkPrefixLength";
public static final String XML_TAG_GATEWAY_ADDRESS = "GatewayAddress";
public static final String XML_TAG_DNS_SERVER_ADDRESSES = "DNSServers";
public static final String XML_TAG_PROXY_SETTINGS = "ProxySettings";
public static final String XML_TAG_PROXY_HOST = "ProxyHost";
public static final String XML_TAG_PROXY_PORT = "ProxyPort";
public static final String XML_TAG_PROXY_PAC_FILE = "ProxyPac";
public static final String XML_TAG_PROXY_EXCLUSION_LIST = "ProxyExclusionList";
private static List parseProxyExclusionListString(
@Nullable String exclusionListString) {
if (exclusionListString == null) {
return Collections.emptyList();
} else {
return Arrays.asList(exclusionListString.toLowerCase(Locale.ROOT).split(","));
}
}
private static String generateProxyExclusionListString(@NonNull String[] exclusionList) {
return TextUtils.join(",", exclusionList);
}
/**
* Write the static IP configuration data elements to XML stream.
*/
private static void writeStaticIpConfigurationToXml(
XmlSerializer out, StaticIpConfiguration staticIpConfiguration)
throws XmlPullParserException, IOException {
if (staticIpConfiguration.getIpAddress() != null) {
XmlUtil.writeNextValue(
out, XML_TAG_LINK_ADDRESS,
staticIpConfiguration.getIpAddress().getAddress().getHostAddress());
XmlUtil.writeNextValue(
out, XML_TAG_LINK_PREFIX_LENGTH,
staticIpConfiguration.getIpAddress().getPrefixLength());
} else {
XmlUtil.writeNextValue(
out, XML_TAG_LINK_ADDRESS, null);
XmlUtil.writeNextValue(
out, XML_TAG_LINK_PREFIX_LENGTH, null);
}
if (staticIpConfiguration.getGateway() != null) {
XmlUtil.writeNextValue(
out, XML_TAG_GATEWAY_ADDRESS,
staticIpConfiguration.getGateway().getHostAddress());
} else {
XmlUtil.writeNextValue(
out, XML_TAG_GATEWAY_ADDRESS, null);
}
// Create a string array of DNS server addresses
String[] dnsServers = new String[staticIpConfiguration.getDnsServers().size()];
int dnsServerIdx = 0;
for (InetAddress inetAddr : staticIpConfiguration.getDnsServers()) {
dnsServers[dnsServerIdx++] = inetAddr.getHostAddress();
}
XmlUtil.writeNextValue(
out, XML_TAG_DNS_SERVER_ADDRESSES, dnsServers);
}
/**
* Write the IP configuration data elements from the provided Configuration to the XML
* stream.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param ipConfiguration IpConfiguration object to be serialized.
*/
public static void writeToXml(XmlSerializer out, IpConfiguration ipConfiguration)
throws XmlPullParserException, IOException {
// Write IP assignment settings
XmlUtil.writeNextValue(out, XML_TAG_IP_ASSIGNMENT,
ipConfiguration.getIpAssignment().toString());
switch (ipConfiguration.getIpAssignment()) {
case STATIC:
writeStaticIpConfigurationToXml(
out, ipConfiguration.getStaticIpConfiguration());
break;
case DHCP:
case UNASSIGNED:
break;
default:
Log.w(TAG, "Ignoring unknown ip assignment type: "
+ ipConfiguration.getIpAssignment());
break;
}
// Write proxy settings
XmlUtil.writeNextValue(
out, XML_TAG_PROXY_SETTINGS,
ipConfiguration.getProxySettings().toString());
switch (ipConfiguration.getProxySettings()) {
case STATIC:
XmlUtil.writeNextValue(
out, XML_TAG_PROXY_HOST,
ipConfiguration.getHttpProxy().getHost());
XmlUtil.writeNextValue(
out, XML_TAG_PROXY_PORT,
ipConfiguration.getHttpProxy().getPort());
XmlUtil.writeNextValue(
out, XML_TAG_PROXY_EXCLUSION_LIST,
generateProxyExclusionListString(
ipConfiguration.getHttpProxy().getExclusionList()));
break;
case PAC:
XmlUtil.writeNextValue(
out, XML_TAG_PROXY_PAC_FILE,
ipConfiguration.getHttpProxy().getPacFileUrl().toString());
break;
case NONE:
case UNASSIGNED:
break;
default:
Log.w(TAG, "Ignoring unknown proxy settings type: "
+ ipConfiguration.getProxySettings());
break;
}
}
/**
* Parse out the static IP configuration from the XML stream.
*/
private static StaticIpConfiguration parseStaticIpConfigurationFromXml(XmlPullParser in)
throws XmlPullParserException, IOException {
StaticIpConfiguration.Builder builder = new StaticIpConfiguration.Builder();
String linkAddressString =
(String) XmlUtil.readNextValueWithName(in, XML_TAG_LINK_ADDRESS);
Integer linkPrefixLength =
(Integer) XmlUtil.readNextValueWithName(in, XML_TAG_LINK_PREFIX_LENGTH);
if (linkAddressString != null && linkPrefixLength != null) {
LinkAddress linkAddress = new LinkAddress(
InetAddresses.parseNumericAddress(linkAddressString),
linkPrefixLength);
if (linkAddress.getAddress() instanceof Inet4Address) {
builder.setIpAddress(linkAddress);
} else {
Log.w(TAG, "Non-IPv4 address: " + linkAddress);
}
}
String gatewayAddressString =
(String) XmlUtil.readNextValueWithName(in, XML_TAG_GATEWAY_ADDRESS);
if (gatewayAddressString != null) {
InetAddress gateway =
InetAddresses.parseNumericAddress(gatewayAddressString);
RouteInfo route = new RouteInfo(null, gateway, null, RouteInfo.RTN_UNICAST);
if (route.isDefaultRoute()
&& route.getDestination().getAddress() instanceof Inet4Address) {
builder.setGateway(gateway);
} else {
Log.w(TAG, "Non-IPv4 default route: " + route);
}
}
String[] dnsServerAddressesString =
(String[]) XmlUtil.readNextValueWithName(in, XML_TAG_DNS_SERVER_ADDRESSES);
if (dnsServerAddressesString != null) {
List dnsServerAddresses = new ArrayList<>();
for (String dnsServerAddressString : dnsServerAddressesString) {
InetAddress dnsServerAddress =
InetAddresses.parseNumericAddress(dnsServerAddressString);
dnsServerAddresses.add(dnsServerAddress);
}
builder.setDnsServers(dnsServerAddresses);
}
return builder.build();
}
/**
* Parses the IP configuration data elements from the provided XML stream to an
* IpConfiguration object.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param outerTagDepth depth of the outer tag in the XML document.
* @return IpConfiguration object if parsing is successful, null otherwise.
*/
public static IpConfiguration parseFromXml(XmlPullParser in, int outerTagDepth)
throws XmlPullParserException, IOException {
IpConfiguration ipConfiguration = new IpConfiguration();
// Parse out the IP assignment info first.
String ipAssignmentString =
(String) XmlUtil.readNextValueWithName(in, XML_TAG_IP_ASSIGNMENT);
IpAssignment ipAssignment = IpAssignment.valueOf(ipAssignmentString);
ipConfiguration.setIpAssignment(ipAssignment);
switch (ipAssignment) {
case STATIC:
ipConfiguration.setStaticIpConfiguration(parseStaticIpConfigurationFromXml(in));
break;
case DHCP:
case UNASSIGNED:
break;
default:
Log.w(TAG, "Ignoring unknown ip assignment type: " + ipAssignment);
break;
}
// Parse out the proxy settings next.
String proxySettingsString =
(String) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_SETTINGS);
ProxySettings proxySettings = ProxySettings.valueOf(proxySettingsString);
ipConfiguration.setProxySettings(proxySettings);
switch (proxySettings) {
case STATIC:
String proxyHost =
(String) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_HOST);
int proxyPort =
(int) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_PORT);
String proxyExclusionList =
(String) XmlUtil.readNextValueWithName(
in, XML_TAG_PROXY_EXCLUSION_LIST);
ipConfiguration.setHttpProxy(
ProxyInfo.buildDirectProxy(
proxyHost, proxyPort,
parseProxyExclusionListString(proxyExclusionList)));
break;
case PAC:
String proxyPacFile =
(String) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_PAC_FILE);
ipConfiguration.setHttpProxy(
ProxyInfo.buildPacProxy(Uri.parse(proxyPacFile)));
break;
case NONE:
case UNASSIGNED:
break;
default:
Log.w(TAG, "Ignoring unknown proxy settings type: " + proxySettings);
break;
}
return ipConfiguration;
}
}
/**
* Utility class to serialize and deserialize {@link NetworkSelectionStatus} object to XML &
* vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module.
*/
public static class NetworkSelectionStatusXmlUtil {
/**
* List of XML tags corresponding to NetworkSelectionStatus object elements.
*/
public static final String XML_TAG_SELECTION_STATUS = "SelectionStatus";
public static final String XML_TAG_DISABLE_REASON = "DisableReason";
public static final String XML_TAG_CONNECT_CHOICE = "ConnectChoice";
public static final String XML_TAG_HAS_EVER_CONNECTED = "HasEverConnected";
public static final String XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED =
"CaptivePortalNeverDetected";
public static final String XML_TAG_CONNECT_CHOICE_RSSI = "ConnectChoiceRssi";
/**
* Write the NetworkSelectionStatus data elements from the provided status to the XML
* stream.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param selectionStatus NetworkSelectionStatus object to be serialized.
*/
public static void writeToXml(XmlSerializer out, NetworkSelectionStatus selectionStatus)
throws XmlPullParserException, IOException {
XmlUtil.writeNextValue(
out, XML_TAG_SELECTION_STATUS, selectionStatus.getNetworkStatusString());
XmlUtil.writeNextValue(
out, XML_TAG_DISABLE_REASON,
selectionStatus.getNetworkSelectionDisableReasonString());
XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE, selectionStatus.getConnectChoice());
XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE_RSSI,
selectionStatus.getConnectChoiceRssi());
XmlUtil.writeNextValue(
out, XML_TAG_HAS_EVER_CONNECTED, selectionStatus.hasEverConnected());
XmlUtil.writeNextValue(out, XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED,
selectionStatus.hasNeverDetectedCaptivePortal());
}
/**
* Parses the NetworkSelectionStatus data elements from the provided XML stream to a
* NetworkSelectionStatus object.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param outerTagDepth depth of the outer tag in the XML document.
* @return NetworkSelectionStatus object if parsing is successful, null otherwise.
*/
public static NetworkSelectionStatus parseFromXml(XmlPullParser in, int outerTagDepth)
throws XmlPullParserException, IOException {
NetworkSelectionStatus selectionStatus = new NetworkSelectionStatus();
String statusString = "";
String disableReasonString = "";
// Initialize hasNeverDetectedCaptivePortal to "false" for upgrading legacy configs
// which do not have the XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED tag.
selectionStatus.setHasNeverDetectedCaptivePortal(false);
// Loop through and parse out all the elements from the stream within this section.
while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
String[] valueName = new String[1];
Object value = XmlUtil.readCurrentValue(in, valueName);
if (valueName[0] == null) {
throw new XmlPullParserException("Missing value name");
}
switch (valueName[0]) {
case XML_TAG_SELECTION_STATUS:
statusString = (String) value;
break;
case XML_TAG_DISABLE_REASON:
disableReasonString = (String) value;
break;
case XML_TAG_CONNECT_CHOICE:
selectionStatus.setConnectChoice((String) value);
break;
case XML_TAG_CONNECT_CHOICE_RSSI:
selectionStatus.setConnectChoiceRssi((int) value);
break;
case XML_TAG_HAS_EVER_CONNECTED:
selectionStatus.setHasEverConnected((boolean) value);
break;
case XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED:
selectionStatus.setHasNeverDetectedCaptivePortal((boolean) value);
default:
Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]);
break;
}
}
// Now figure out the network selection status codes from |selectionStatusString| &
// |disableReasonString|.
int status =
Arrays.asList(NetworkSelectionStatus.QUALITY_NETWORK_SELECTION_STATUS)
.indexOf(statusString);
int disableReason =
NetworkSelectionStatus.getDisableReasonByString(disableReasonString);
// If either of the above codes are invalid or if the network was temporarily disabled
// (blacklisted), restore the status as enabled. We don't want to persist blacklists
// across reboots.
if (status == -1 || disableReason == -1 ||
status == NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED) {
status = NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
disableReason = NetworkSelectionStatus.DISABLED_NONE;
}
selectionStatus.setNetworkSelectionStatus(status);
selectionStatus.setNetworkSelectionDisableReason(disableReason);
if (status == NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED) {
// Make the counter non-zero so that logging code works properly
selectionStatus.setDisableReasonCounter(disableReason, 1);
}
return selectionStatus;
}
}
/**
* Utility class to serialize and deseriaize {@link WifiEnterpriseConfig} object to XML &
* vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module.
*/
public static class WifiEnterpriseConfigXmlUtil {
/**
* List of XML tags corresponding to WifiEnterpriseConfig object elements.
*/
public static final String XML_TAG_IDENTITY = "Identity";
public static final String XML_TAG_ANON_IDENTITY = "AnonIdentity";
public static final String XML_TAG_PASSWORD = "Password";
public static final String XML_TAG_CLIENT_CERT = "ClientCert";
public static final String XML_TAG_CA_CERT = "CaCert";
public static final String XML_TAG_SUBJECT_MATCH = "SubjectMatch";
public static final String XML_TAG_ENGINE = "Engine";
public static final String XML_TAG_ENGINE_ID = "EngineId";
public static final String XML_TAG_PRIVATE_KEY_ID = "PrivateKeyId";
public static final String XML_TAG_ALT_SUBJECT_MATCH = "AltSubjectMatch";
public static final String XML_TAG_DOM_SUFFIX_MATCH = "DomSuffixMatch";
public static final String XML_TAG_CA_PATH = "CaPath";
public static final String XML_TAG_EAP_METHOD = "EapMethod";
public static final String XML_TAG_PHASE2_METHOD = "Phase2Method";
public static final String XML_TAG_PLMN = "PLMN";
public static final String XML_TAG_REALM = "Realm";
public static final String XML_TAG_OCSP = "Ocsp";
public static final String XML_TAG_WAPI_CERT_SUITE = "WapiCertSuite";
public static final String XML_TAG_APP_INSTALLED_ROOT_CA_CERT = "AppInstalledRootCaCert";
public static final String XML_TAG_APP_INSTALLED_PRIVATE_KEY = "AppInstalledPrivateKey";
public static final String XML_TAG_KEYCHAIN_KEY_ALIAS = "KeyChainAlias";
public static final String XML_TAG_DECORATED_IDENTITY_PREFIX = "DecoratedIdentityPrefix";
/**
* Write password key to the XML stream.
*
* If encryptionUtil is null or if encryption fails for some reason, the password is stored
* in plaintext, else the encrypted psk is stored.
*/
private static void writePasswordToXml(
XmlSerializer out, String password,
@Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
throws XmlPullParserException, IOException {
EncryptedData encryptedData = null;
if (encryptionUtil != null) {
if (password != null) {
encryptedData = encryptionUtil.encrypt(password.getBytes());
if (encryptedData == null) {
// We silently fail encryption failures!
Log.wtf(TAG, "Encryption of password failed");
}
}
}
if (encryptedData != null) {
XmlUtil.writeNextSectionStart(out, XML_TAG_PASSWORD);
EncryptedDataXmlUtil.writeToXml(out, encryptedData);
XmlUtil.writeNextSectionEnd(out, XML_TAG_PASSWORD);
} else {
XmlUtil.writeNextValue(out, XML_TAG_PASSWORD, password);
}
}
/**
* Write the WifiEnterpriseConfig data elements from the provided config to the XML
* stream.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param enterpriseConfig WifiEnterpriseConfig object to be serialized.
* @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}.
*/
public static void writeToXml(XmlSerializer out, WifiEnterpriseConfig enterpriseConfig,
@Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
throws XmlPullParserException, IOException {
XmlUtil.writeNextValue(out, XML_TAG_IDENTITY,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.IDENTITY_KEY));
XmlUtil.writeNextValue(out, XML_TAG_ANON_IDENTITY,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY));
writePasswordToXml(
out, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY),
encryptionUtil);
XmlUtil.writeNextValue(out, XML_TAG_CLIENT_CERT,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY));
XmlUtil.writeNextValue(out, XML_TAG_CA_CERT,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY));
XmlUtil.writeNextValue(out, XML_TAG_SUBJECT_MATCH,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY));
XmlUtil.writeNextValue(out, XML_TAG_ENGINE,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY));
XmlUtil.writeNextValue(out, XML_TAG_ENGINE_ID,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY));
XmlUtil.writeNextValue(out, XML_TAG_PRIVATE_KEY_ID,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY));
XmlUtil.writeNextValue(out, XML_TAG_ALT_SUBJECT_MATCH,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY));
XmlUtil.writeNextValue(out, XML_TAG_DOM_SUFFIX_MATCH,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY));
XmlUtil.writeNextValue(out, XML_TAG_CA_PATH,
enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CA_PATH_KEY));
XmlUtil.writeNextValue(out, XML_TAG_EAP_METHOD, enterpriseConfig.getEapMethod());
XmlUtil.writeNextValue(out, XML_TAG_PHASE2_METHOD, enterpriseConfig.getPhase2Method());
XmlUtil.writeNextValue(out, XML_TAG_PLMN, enterpriseConfig.getPlmn());
XmlUtil.writeNextValue(out, XML_TAG_REALM, enterpriseConfig.getRealm());
XmlUtil.writeNextValue(out, XML_TAG_OCSP, enterpriseConfig.getOcsp());
XmlUtil.writeNextValue(out,
XML_TAG_WAPI_CERT_SUITE, enterpriseConfig.getWapiCertSuite());
XmlUtil.writeNextValue(out, XML_TAG_APP_INSTALLED_ROOT_CA_CERT,
enterpriseConfig.isAppInstalledCaCert());
XmlUtil.writeNextValue(out, XML_TAG_APP_INSTALLED_PRIVATE_KEY,
enterpriseConfig.isAppInstalledDeviceKeyAndCert());
XmlUtil.writeNextValue(out, XML_TAG_KEYCHAIN_KEY_ALIAS,
enterpriseConfig.getClientKeyPairAliasInternal());
if (SdkLevel.isAtLeastS()) {
XmlUtil.writeNextValue(out, XML_TAG_DECORATED_IDENTITY_PREFIX,
enterpriseConfig.getDecoratedIdentityPrefix());
}
}
/**
* Parses the data elements from the provided XML stream to a WifiEnterpriseConfig object.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param outerTagDepth depth of the outer tag in the XML document.
* @param shouldExpectEncryptedCredentials Whether to expect encrypted credentials or not.
* @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}.
* @return WifiEnterpriseConfig object if parsing is successful, null otherwise.
*/
public static WifiEnterpriseConfig parseFromXml(XmlPullParser in, int outerTagDepth,
boolean shouldExpectEncryptedCredentials,
@Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
throws XmlPullParserException, IOException {
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
// Loop through and parse out all the elements from the stream within this section.
while (XmlUtilHelper.nextElementWithin(in, outerTagDepth)) {
if (in.getAttributeValue(null, "name") != null) {
// Value elements.
String[] valueName = new String[1];
Object value = XmlUtil.readCurrentValue(in, valueName);
if (valueName[0] == null) {
throw new XmlPullParserException("Missing value name");
}
switch (valueName[0]) {
case XML_TAG_IDENTITY:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.IDENTITY_KEY, (String) value);
break;
case XML_TAG_ANON_IDENTITY:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.ANON_IDENTITY_KEY, (String) value);
break;
case XML_TAG_PASSWORD:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.PASSWORD_KEY, (String) value);
if (shouldExpectEncryptedCredentials
&& !TextUtils.isEmpty(enterpriseConfig.getFieldValue(
WifiEnterpriseConfig.PASSWORD_KEY))) {
// Indicates that encryption of password failed when it was last
// written.
Log.e(TAG, "password value not expected");
}
break;
case XML_TAG_CLIENT_CERT:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.CLIENT_CERT_KEY, (String) value);
break;
case XML_TAG_CA_CERT:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.CA_CERT_KEY, (String) value);
break;
case XML_TAG_SUBJECT_MATCH:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.SUBJECT_MATCH_KEY, (String) value);
break;
case XML_TAG_ENGINE:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.ENGINE_KEY, (String) value);
break;
case XML_TAG_ENGINE_ID:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.ENGINE_ID_KEY, (String) value);
break;
case XML_TAG_PRIVATE_KEY_ID:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, (String) value);
break;
case XML_TAG_ALT_SUBJECT_MATCH:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, (String) value);
break;
case XML_TAG_DOM_SUFFIX_MATCH:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, (String) value);
break;
case XML_TAG_CA_PATH:
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.CA_PATH_KEY, (String) value);
break;
case XML_TAG_OCSP:
enterpriseConfig.setOcsp((int) value);
break;
case XML_TAG_EAP_METHOD:
enterpriseConfig.setEapMethod((int) value);
break;
case XML_TAG_PHASE2_METHOD:
enterpriseConfig.setPhase2Method((int) value);
break;
case XML_TAG_PLMN:
enterpriseConfig.setPlmn((String) value);
break;
case XML_TAG_REALM:
enterpriseConfig.setRealm((String) value);
break;
case XML_TAG_WAPI_CERT_SUITE:
enterpriseConfig.setWapiCertSuite((String) value);
break;
case XML_TAG_APP_INSTALLED_ROOT_CA_CERT:
enterpriseConfig.initIsAppInstalledCaCert((boolean) value);
break;
case XML_TAG_APP_INSTALLED_PRIVATE_KEY:
enterpriseConfig.initIsAppInstalledDeviceKeyAndCert((boolean) value);
break;
case XML_TAG_KEYCHAIN_KEY_ALIAS:
if (SdkLevel.isAtLeastS()) {
enterpriseConfig.setClientKeyPairAlias((String) value);
}
break;
case XML_TAG_DECORATED_IDENTITY_PREFIX:
if (SdkLevel.isAtLeastS()) {
enterpriseConfig.setDecoratedIdentityPrefix((String) value);
}
break;
default:
Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]);
break;
}
} else {
String tagName = in.getName();
if (tagName == null) {
throw new XmlPullParserException("Unexpected null tag found");
}
switch (tagName) {
case XML_TAG_PASSWORD:
if (!shouldExpectEncryptedCredentials || encryptionUtil == null) {
throw new XmlPullParserException(
"encrypted password section not expected");
}
EncryptedData encryptedData =
EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1);
byte[] passwordBytes = encryptionUtil.decrypt(encryptedData);
if (passwordBytes == null) {
Log.wtf(TAG, "Decryption of password failed");
} else {
enterpriseConfig.setFieldValue(
WifiEnterpriseConfig.PASSWORD_KEY,
new String(passwordBytes));
}
break;
default:
Log.w(TAG, "Ignoring unknown tag name found: " + tagName);
break;
}
}
}
return enterpriseConfig;
}
}
/**
* Utility class to serialize and deseriaize {@link EncryptedData} object to XML &
* vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module.
*/
public static class EncryptedDataXmlUtil {
/**
* List of XML tags corresponding to EncryptedData object elements.
*/
private static final String XML_TAG_ENCRYPTED_DATA = "EncryptedData";
private static final String XML_TAG_IV = "IV";
/**
* Write the NetworkSelectionStatus data elements from the provided status to the XML
* stream.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param encryptedData EncryptedData object to be serialized.
*/
public static void writeToXml(XmlSerializer out, EncryptedData encryptedData)
throws XmlPullParserException, IOException {
XmlUtil.writeNextValue(
out, XML_TAG_ENCRYPTED_DATA, encryptedData.getEncryptedData());
XmlUtil.writeNextValue(out, XML_TAG_IV, encryptedData.getIv());
}
/**
* Parses the EncryptedData data elements from the provided XML stream to a
* EncryptedData object.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param outerTagDepth depth of the outer tag in the XML document.
* @return EncryptedData object if parsing is successful, null otherwise.
*/
public static EncryptedData parseFromXml(XmlPullParser in, int outerTagDepth)
throws XmlPullParserException, IOException {
byte[] encryptedData = null;
byte[] iv = null;
// Loop through and parse out all the elements from the stream within this section.
while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
String[] valueName = new String[1];
Object value = XmlUtil.readCurrentValue(in, valueName);
if (valueName[0] == null) {
throw new XmlPullParserException("Missing value name");
}
switch (valueName[0]) {
case XML_TAG_ENCRYPTED_DATA:
encryptedData = (byte[]) value;
break;
case XML_TAG_IV:
iv = (byte[]) value;
break;
default:
Log.e(TAG, "Unknown value name found: " + valueName[0]);
break;
}
}
return new EncryptedData(encryptedData, iv);
}
}
public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
throws IOException, XmlPullParserException {
return XmlUtilHelper.nextElementWithin(parser, outerDepth);
}
/**
* Utility class to serialize and deseriaize {@link SoftApConfiguration} object to XML
* & vice versa. This is used by both {@link com.android.server.wifi.SoftApStore} modules.
*/
public static class SoftApConfigurationXmlUtil {
/**
* List of XML tags corresponding to SoftApConfiguration object elements.
*/
public static final String XML_TAG_CLIENT_MACADDRESS = "ClientMacAddress";
public static final String XML_TAG_BAND_CHANNEL = "BandChannel";
private static final String XML_TAG_BAND = "Band";
private static final String XML_TAG_CHANNEL = "Channel";
/**
* Parses the client list from the provided XML stream to a ArrayList object.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param outerTagDepth depth of the outer tag in the XML document.
* @return ArrayList object if parsing is successful, null otherwise.
*/
public static List parseClientListFromXml(XmlPullParser in,
int outerTagDepth) throws XmlPullParserException, IOException,
IllegalArgumentException {
List clientList = new ArrayList<>();
// Loop through and parse out all the elements from the stream within this section.
while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
String[] valueName = new String[1];
Object value = XmlUtil.readCurrentValue(in, valueName);
if (valueName[0] == null) {
throw new XmlPullParserException("Missing value name");
}
switch (valueName[0]) {
case XML_TAG_CLIENT_MACADDRESS:
MacAddress client = MacAddress.fromString((String) value);
clientList.add(client);
break;
default:
Log.e(TAG, "Unknown value name found: " + valueName[0]);
break;
}
}
return clientList;
}
/**
* Write the SoftApConfiguration client control list data elements
* from the provided list to the XML stream.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param clientList Client list object to be serialized.
*/
public static void writeClientListToXml(XmlSerializer out, List clientList)
throws XmlPullParserException, IOException {
for (MacAddress mac: clientList) {
XmlUtil.writeNextValue(out, XML_TAG_CLIENT_MACADDRESS, mac.toString());
}
}
/**
* Parses the band and channel from the provided XML stream to a SparseIntArray object.
*
* @param in XmlPullParser instance pointing to the XML stream.
* @param outerTagDepth depth of the outer tag in the XML document.
* @return SparseIntArray object if parsing is successful, null otherwise.
*/
public static SparseIntArray parseChannelsFromXml(XmlPullParser in,
int outerTagDepth) throws XmlPullParserException, IOException,
IllegalArgumentException {
SparseIntArray channels = new SparseIntArray();
while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
int band = ApConfigUtil.INVALID_VALUE_FOR_BAND_OR_CHANNEL;
int channel = ApConfigUtil.INVALID_VALUE_FOR_BAND_OR_CHANNEL;
switch (in.getName()) {
case XML_TAG_BAND_CHANNEL:
while (!XmlUtil.isNextSectionEnd(in, outerTagDepth + 1)) {
String[] valueName = new String[1];
Object value = XmlUtil.readCurrentValue(in, valueName);
if (valueName[0] == null) {
throw new XmlPullParserException("Missing value name");
}
switch (valueName[0]) {
case XML_TAG_BAND:
band = (int) value;
break;
case XML_TAG_CHANNEL:
channel = (int) value;
break;
default:
Log.e(TAG, "Unknown value name found: " + valueName[0]);
break;
}
}
channels.put(band, channel);
break;
}
}
return channels;
}
/**
* Write the SoftApConfiguration channels data elements
* from the provided SparseIntArray to the XML stream.
*
* @param out XmlSerializer instance pointing to the XML stream.
* @param channels SparseIntArray, which includes bands and channels, to be serialized.
*/
public static void writeChannelsToXml(XmlSerializer out, SparseIntArray channels)
throws XmlPullParserException, IOException {
for (int i = 0; i < channels.size(); i++) {
XmlUtil.writeNextSectionStart(out, XML_TAG_BAND_CHANNEL);
XmlUtil.writeNextValue(out, XML_TAG_BAND, channels.keyAt(i));
XmlUtil.writeNextValue(out, XML_TAG_CHANNEL, channels.valueAt(i));
XmlUtil.writeNextSectionEnd(out, XML_TAG_BAND_CHANNEL);
}
}
}
}