content: /*
   * Copyright (C) 2016 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.wifi;
  
  import android.net.IpConfiguration;
  import android.net.wifi.WifiConfiguration;
  import android.net.wifi.WifiEnterpriseConfig;
  import android.os.Process;
  import android.util.Log;
  import android.util.SparseArray;
  import android.util.Xml;
  
  import com.android.internal.util.FastXmlSerializer;
  import com.android.server.net.IpConfigStore;
  import com.android.server.wifi.util.NativeUtil;
  import com.android.server.wifi.util.WifiPermissionsUtil;
  import com.android.server.wifi.util.XmlUtil;
  import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil;
  import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  import org.xmlpull.v1.XmlSerializer;
  
  import java.io.BufferedReader;
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.CharArrayReader;
  import java.io.FileDescriptor;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.io.UnsupportedEncodingException;
  import java.nio.charset.StandardCharsets;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Class used to backup/restore data using the SettingsBackupAgent.
   * There are 2 symmetric API's exposed here:
   * 1. retrieveBackupDataFromConfigurations: Retrieve the configuration data to be backed up.
   * 2. retrieveConfigurationsFromBackupData: Restore the configuration using the provided data.
   * The byte stream to be backed up is XML encoded and versioned to migrate the data easily across
   * revisions.
   */
  public class WifiBackupRestore {
      private static final String TAG = "WifiBackupRestore";
  
      /**
       * Current backup data version.
       * Note: before Android P this used to be an {@code int}, however support for minor versions
       * has been added in Android P. Currently this field is a {@code float} representing
       * "majorVersion.minorVersion" of the backed up data. MinorVersion starts with 0 and should
       * be incremented when necessary. MajorVersion starts with 1 and bumping it up requires
       * also resetting minorVersion to 0.
       *
       * MajorVersion will be incremented for modifications of the XML schema, excluding additive
       * modifications in <WifiConfiguration> and/or <IpConfiguration> tags.
       * Should the major version be bumped up, a new {@link WifiBackupDataParser} parser needs to
       * be added and returned from {@link #getWifiBackupDataParser(int)} ()}.
       * Note that bumping up the major version will result in inability to restore the backup
       * set to those lower versions of SDK_INT that don't support the version.
       *
       * MinorVersion will only be incremented for addition of <WifiConfiguration> and/or
       * <IpConfiguration> tags. Any other modifications to the schema should result in bumping up
       * the major version and resetting the minor version to 0.
       * Note that bumping up only the minor version will still allow restoring the backup set to
       * lower versions of SDK_INT.
       */
      private static final float CURRENT_BACKUP_DATA_VERSION = 1.1f;
  
      /** This list of older versions will be used to restore data from older backups. */
      /**
       * First version of the backup data format.
       */
      private static final int INITIAL_BACKUP_DATA_VERSION = 1;
  
      /**
       * List of XML section header tags in the backed up data
       */
      private static final String XML_TAG_DOCUMENT_HEADER = "WifiBackupData";
      private static final String XML_TAG_VERSION = "Version";
  
      static final String XML_TAG_SECTION_HEADER_NETWORK_LIST = "NetworkList";
      static final String XML_TAG_SECTION_HEADER_NETWORK = "Network";
      static final String XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION = "WifiConfiguration";
      static final String XML_TAG_SECTION_HEADER_IP_CONFIGURATION = "IpConfiguration";
  
      /**
       * Regex to mask out passwords in backup data dump.
       */
      private static final String PSK_MASK_LINE_MATCH_PATTERN =
              "<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>.*<.*>";
      private static final String PSK_MASK_SEARCH_PATTERN =
              "(<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>)(.*)(<.*>)";
      private static final String PSK_MASK_REPLACE_PATTERN = "$1*$3";
  
      private static final String WEP_KEYS_MASK_LINE_START_MATCH_PATTERN =
              "<string-array.*" + WifiConfigurationXmlUtil.XML_TAG_WEP_KEYS + ".*num=\"[0-9]\">";
      private static final String WEP_KEYS_MASK_LINE_END_MATCH_PATTERN = "</string-array>";
      private static final String WEP_KEYS_MASK_SEARCH_PATTERN = "(<.*=)(.*)(/>)";
      private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*$3";
  
      private final WifiPermissionsUtil mWifiPermissionsUtil;
      /**
       * Verbose logging flag.
       */
      private boolean mVerboseLoggingEnabled = false;
  
      /**
       * Store the dump of the backup/restore data for debugging. This is only stored when verbose
       * logging is enabled in developer options.
       */
      private byte[] mDebugLastBackupDataRetrieved;
      private byte[] mDebugLastBackupDataRestored;
      private byte[] mDebugLastSupplicantBackupDataRestored;
  
      public WifiBackupRestore(WifiPermissionsUtil wifiPermissionsUtil) {
          mWifiPermissionsUtil = wifiPermissionsUtil;
      }
  
      /**
       * Retrieve an XML byte stream representing the data that needs to be backed up from the
       * provided configurations.
       *
       * @param configurations list of currently saved networks that needs to be backed up.
       * @return Raw byte stream of XML that needs to be backed up.
       */
      public byte[] retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations) {
          if (configurations == null) {
              Log.e(TAG, "Invalid configuration list received");
              return new byte[0];
          }
  
          try {
              final XmlSerializer out = new FastXmlSerializer();
              final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
              out.setOutput(outputStream, StandardCharsets.UTF_8.name());
  
              // Start writing the XML stream.
              XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
  
              XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_BACKUP_DATA_VERSION);
  
              writeNetworkConfigurationsToXml(out, configurations);
  
              XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
  
              byte[] data = outputStream.toByteArray();
  
              if (mVerboseLoggingEnabled) {
                  mDebugLastBackupDataRetrieved = data;
              }
  
              return data;
          } catch (XmlPullParserException e) {
              Log.e(TAG, "Error retrieving the backup data: " + e);
          } catch (IOException e) {
              Log.e(TAG, "Error retrieving the backup data: " + e);
          }
          return new byte[0];
      }
  
      /**
       * Write the list of configurations to the XML stream.
       */
      private void writeNetworkConfigurationsToXml(
              XmlSerializer out, List<WifiConfiguration> configurations)
              throws XmlPullParserException, IOException {
          XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
          for (WifiConfiguration configuration : configurations) {
              // We don't want to backup/restore enterprise/passpoint configurations.
              if (configuration.isEnterprise() || configuration.isPasspoint()) {
                  continue;
              }
              if (!mWifiPermissionsUtil.checkConfigOverridePermission(configuration.creatorUid)) {
                  Log.d(TAG, "Ignoring network from an app with no config override permission: "
                          + configuration.configKey());
                  continue;
              }
              // Write this configuration data now.
              XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK);
              writeNetworkConfigurationToXml(out, configuration);
              XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK);
          }
          XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
      }
  
      /**
       * Write the configuration data elements from the provided Configuration to the XML stream.
       * Uses XmlUtils to write the values of each element.
       */
      private void writeNetworkConfigurationToXml(XmlSerializer out, WifiConfiguration configuration)
              throws XmlPullParserException, IOException {
          XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
          WifiConfigurationXmlUtil.writeToXmlForBackup(out, configuration);
          XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
          XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
          IpConfigurationXmlUtil.writeToXml(out, configuration.getIpConfiguration());
          XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
      }
  
      /**
       * Parse out the configurations from the back up data.
       *
       * @param data raw byte stream representing the XML data.
       * @return list of networks retrieved from the backed up data.
       */
      public List<WifiConfiguration> retrieveConfigurationsFromBackupData(byte[] data) {
          if (data == null || data.length == 0) {
              Log.e(TAG, "Invalid backup data received");
              return null;
          }
          try {
              if (mVerboseLoggingEnabled) {
                  mDebugLastBackupDataRestored = data;
              }
  
              final XmlPullParser in = Xml.newPullParser();
              ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
              in.setInput(inputStream, StandardCharsets.UTF_8.name());
  
              // Start parsing the XML stream.
              XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
              int rootTagDepth = in.getDepth();
  
              int majorVersion = -1;
              int minorVersion = -1;
              try {
                  float version = (float) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION);
  
                  // parse out major and minor versions
                  String versionStr = new Float(version).toString();
                  int separatorPos = versionStr.indexOf('.');
                  if (separatorPos == -1) {
                      majorVersion = Integer.parseInt(versionStr);
                      minorVersion = 0;
                  } else {
                      majorVersion = Integer.parseInt(versionStr.substring(0, separatorPos));
                      minorVersion = Integer.parseInt(versionStr.substring(separatorPos + 1));
                  }
              } catch (ClassCastException cce) {
                  // Integer cannot be cast to Float for data coming from before Android P
                  majorVersion = 1;
                  minorVersion = 0;
              }
              Log.d(TAG, "Version of backup data - major: " + majorVersion
                      + "; minor: " + minorVersion);
  
              WifiBackupDataParser parser = getWifiBackupDataParser(majorVersion);
              if (parser == null) {
                  Log.w(TAG, "Major version of backup data is unknown to this Android"
                          + " version; not restoring");
                  return null;
              } else {
                  return parser.parseNetworkConfigurationsFromXml(in, rootTagDepth, minorVersion);
              }
          } catch (XmlPullParserException | IOException | ClassCastException
                  | IllegalArgumentException e) {
              Log.e(TAG, "Error parsing the backup data: " + e);
          }
          return null;
      }
  
      private WifiBackupDataParser getWifiBackupDataParser(int majorVersion) {
          switch (majorVersion) {
              case INITIAL_BACKUP_DATA_VERSION:
                  return new WifiBackupDataV1Parser();
              default:
                  Log.e(TAG, "Unrecognized majorVersion of backup data: " + majorVersion);
                  return null;
          }
      }
  
      /**
       * Create log dump of the backup data in XML format with the preShared & WEP key masked.
       *
       * PSK keys are written in the following format in XML:
       * <string name="PreSharedKey">WifiBackupRestorePsk</string>
       *
       * WEP Keys are written in following format in XML:
       * <string-array name="WEPKeys" num="4">
       *  <item value="WifiBackupRestoreWep1" />
       *  <item value="WifiBackupRestoreWep2" />
       *  <item value="WifiBackupRestoreWep3" />
       *  <item value="WifiBackupRestoreWep3" />
       * </string-array>
       */
      private String createLogFromBackupData(byte[] data) {
          StringBuilder sb = new StringBuilder();
          try {
              String xmlString = new String(data, StandardCharsets.UTF_8.name());
              boolean wepKeysLine = false;
              for (String line : xmlString.split("\n")) {
                  if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) {
                      line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN);
                  }
                  if (line.matches(WEP_KEYS_MASK_LINE_START_MATCH_PATTERN)) {
                      wepKeysLine = true;
                  } else if (line.matches(WEP_KEYS_MASK_LINE_END_MATCH_PATTERN)) {
                      wepKeysLine = false;
                  } else if (wepKeysLine) {
                      line = line.replaceAll(
                              WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN);
                  }
                  sb.append(line).append("\n");
              }
          } catch (UnsupportedEncodingException e) {
              return "";
          }
          return sb.toString();
      }
  
      /**
       * Restore state from the older supplicant back up data.
       * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file.
       *
       * @param supplicantData Raw byte stream of wpa_supplicant.conf
       * @param ipConfigData   Raw byte stream of ipconfig.txt
       * @return list of networks retrieved from the backed up data.
       */
      public List<WifiConfiguration> retrieveConfigurationsFromSupplicantBackupData(
              byte[] supplicantData, byte[] ipConfigData) {
          if (supplicantData == null || supplicantData.length == 0) {
              Log.e(TAG, "Invalid supplicant backup data received");
              return null;
          }
  
          if (mVerboseLoggingEnabled) {
              mDebugLastSupplicantBackupDataRestored = supplicantData;
          }
  
          SupplicantBackupMigration.SupplicantNetworks supplicantNetworks =
                  new SupplicantBackupMigration.SupplicantNetworks();
          // Incorporate the networks present in the backup data.
          char[] restoredAsChars = new char[supplicantData.length];
          for (int i = 0; i < supplicantData.length; i++) {
              restoredAsChars[i] = (char) supplicantData[i];
          }
  
          BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsChars));
          supplicantNetworks.readNetworksFromStream(in);
  
          // Retrieve corresponding WifiConfiguration objects.
          List<WifiConfiguration> configurations = supplicantNetworks.retrieveWifiConfigurations();
  
          // Now retrieve all the IpConfiguration objects and set in the corresponding
          // WifiConfiguration objects if ipconfig data is present.
          if (ipConfigData != null && ipConfigData.length != 0) {
              SparseArray<IpConfiguration> networks =
                      IpConfigStore.readIpAndProxyConfigurations(
                              new ByteArrayInputStream(ipConfigData));
              if (networks != null) {
                  for (int i = 0; i < networks.size(); i++) {
                      int id = networks.keyAt(i);
                      for (WifiConfiguration configuration : configurations) {
                          // This is a dangerous lookup, but that's how it is currently written.
                          if (configuration.configKey().hashCode() == id) {
                              configuration.setIpConfiguration(networks.valueAt(i));
                          }
                      }
                  }
              } else {
                  Log.e(TAG, "Failed to parse ipconfig data");
              }
          } else {
              Log.e(TAG, "Invalid ipconfig backup data received");
          }
          return configurations;
      }
  
      /**
       * Enable verbose logging.
       *
       * @param verbose verbosity level.
       */
      public void enableVerboseLogging(int verbose) {
          mVerboseLoggingEnabled = (verbose > 0);
          if (!mVerboseLoggingEnabled) {
              mDebugLastBackupDataRetrieved = null;
              mDebugLastBackupDataRestored = null;
              mDebugLastSupplicantBackupDataRestored = null;
          }
      }
  
      /**
       * Dump out the last backup/restore data if verbose logging is enabled.
       *
       * @param fd   unused
       * @param pw   PrintWriter for writing dump to
       * @param args unused
       */
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println("Dump of WifiBackupRestore");
          if (mDebugLastBackupDataRetrieved != null) {
              pw.println("Last backup data retrieved: "
                      + createLogFromBackupData(mDebugLastBackupDataRetrieved));
          }
          if (mDebugLastBackupDataRestored != null) {
              pw.println("Last backup data restored: "
                      + createLogFromBackupData(mDebugLastBackupDataRestored));
          }
          if (mDebugLastSupplicantBackupDataRestored != null) {
              pw.println("Last old backup data restored: "
                      + SupplicantBackupMigration.createLogFromBackupData(
                              mDebugLastSupplicantBackupDataRestored));
          }
      }
  
      /**
       * These sub classes contain the logic to parse older backups and restore wifi state from it.
       * Most of the code here has been migrated over from BackupSettingsAgent.
       * This is kind of ugly text parsing, but it is needed to support the migration of this data.
       */
      public static class SupplicantBackupMigration {
          /**
           * List of keys to look out for in wpa_supplicant.conf parsing.
           * These key values are declared in different parts of the wifi codebase today.
           */
          public static final String SUPPLICANT_KEY_SSID = WifiConfiguration.ssidVarName;
          public static final String SUPPLICANT_KEY_HIDDEN = WifiConfiguration.hiddenSSIDVarName;
          public static final String SUPPLICANT_KEY_KEY_MGMT = WifiConfiguration.KeyMgmt.varName;
          public static final String SUPPLICANT_KEY_CLIENT_CERT =
                  WifiEnterpriseConfig.CLIENT_CERT_KEY;
          public static final String SUPPLICANT_KEY_CA_CERT = WifiEnterpriseConfig.CA_CERT_KEY;
          public static final String SUPPLICANT_KEY_CA_PATH = WifiEnterpriseConfig.CA_PATH_KEY;
          public static final String SUPPLICANT_KEY_EAP = WifiEnterpriseConfig.EAP_KEY;
          public static final String SUPPLICANT_KEY_PSK = WifiConfiguration.pskVarName;
          public static final String SUPPLICANT_KEY_WEP_KEY0 = WifiConfiguration.wepKeyVarNames[0];
          public static final String SUPPLICANT_KEY_WEP_KEY1 = WifiConfiguration.wepKeyVarNames[1];
          public static final String SUPPLICANT_KEY_WEP_KEY2 = WifiConfiguration.wepKeyVarNames[2];
          public static final String SUPPLICANT_KEY_WEP_KEY3 = WifiConfiguration.wepKeyVarNames[3];
          public static final String SUPPLICANT_KEY_WEP_KEY_IDX =
                  WifiConfiguration.wepTxKeyIdxVarName;
          public static final String SUPPLICANT_KEY_ID_STR = "id_str";
  
          /**
           * Regex to mask out passwords in backup data dump.
           */
          private static final String PSK_MASK_LINE_MATCH_PATTERN =
                  ".*" + SUPPLICANT_KEY_PSK + ".*=.*";
          private static final String PSK_MASK_SEARCH_PATTERN =
                  "(.*" + SUPPLICANT_KEY_PSK + ".*=)(.*)";
          private static final String PSK_MASK_REPLACE_PATTERN = "$1*";
  
          private static final String WEP_KEYS_MASK_LINE_MATCH_PATTERN =
                  ".*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=.*";
          private static final String WEP_KEYS_MASK_SEARCH_PATTERN =
                  "(.*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=)(.*)";
          private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*";
  
          /**
           * Create log dump of the backup data in wpa_supplicant.conf format with the preShared &
           * WEP key masked.
           *
           * PSK keys are written in the following format in wpa_supplicant.conf:
           *  psk=WifiBackupRestorePsk
           *
           * WEP Keys are written in following format in wpa_supplicant.conf:
           *  wep_keys0=WifiBackupRestoreWep0
           *  wep_keys1=WifiBackupRestoreWep1
           *  wep_keys2=WifiBackupRestoreWep2
           *  wep_keys3=WifiBackupRestoreWep3
           */
          public static String createLogFromBackupData(byte[] data) {
              StringBuilder sb = new StringBuilder();
              try {
                  String supplicantConfString = new String(data, StandardCharsets.UTF_8.name());
                  for (String line : supplicantConfString.split("\n")) {
                      if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) {
                          line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN);
                      }
                      if (line.matches(WEP_KEYS_MASK_LINE_MATCH_PATTERN)) {
                          line = line.replaceAll(
                                  WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN);
                      }
                      sb.append(line).append("\n");
                  }
              } catch (UnsupportedEncodingException e) {
                  return "";
              }
              return sb.toString();
          }
  
          /**
           * Class for capturing a network definition from the wifi supplicant config file.
           */
          static class SupplicantNetwork {
              private String mParsedSSIDLine;
              private String mParsedHiddenLine;
              private String mParsedKeyMgmtLine;
              private String mParsedPskLine;
              private String[] mParsedWepKeyLines = new String[4];
              private String mParsedWepTxKeyIdxLine;
              private String mParsedIdStrLine;
              public boolean certUsed = false;
              public boolean isEap = false;
  
              /**
               * Read lines from wpa_supplicant.conf stream for this network.
               */
              public static SupplicantNetwork readNetworkFromStream(BufferedReader in) {
                  final SupplicantNetwork n = new SupplicantNetwork();
                  String line;
                  try {
                      while (in.ready()) {
                          line = in.readLine();
                          if (line == null || line.startsWith("}")) {
                              break;
                          }
                          n.parseLine(line);
                      }
                  } catch (IOException e) {
                      return null;
                  }
                  return n;
              }
  
              /**
               * Parse a line from wpa_supplicant.conf stream for this network.
               */
              void parseLine(String line) {
                  // Can't rely on particular whitespace patterns so strip leading/trailing.
                  line = line.trim();
                  if (line.isEmpty()) return; // only whitespace; drop the line.
  
                  // Now parse the network block within wpa_supplicant.conf and store the important
                  // lines for processing later.
                  if (line.startsWith(SUPPLICANT_KEY_SSID + "=")) {
                      mParsedSSIDLine = line;
                  } else if (line.startsWith(SUPPLICANT_KEY_HIDDEN + "=")) {
                      mParsedHiddenLine = line;
                  } else if (line.startsWith(SUPPLICANT_KEY_KEY_MGMT + "=")) {
                      mParsedKeyMgmtLine = line;
                      if (line.contains("EAP")) {
                          isEap = true;
                      }
                  } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT + "=")) {
                      certUsed = true;
                  } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT + "=")) {
                      certUsed = true;
                  } else if (line.startsWith(SUPPLICANT_KEY_CA_PATH + "=")) {
                      certUsed = true;
                  } else if (line.startsWith(SUPPLICANT_KEY_EAP + "=")) {
                      isEap = true;
                  } else if (line.startsWith(SUPPLICANT_KEY_PSK + "=")) {
                      mParsedPskLine = line;
                  } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY0 + "=")) {
                      mParsedWepKeyLines[0] = line;
                  } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY1 + "=")) {
                      mParsedWepKeyLines[1] = line;
                  } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY2 + "=")) {
                      mParsedWepKeyLines[2] = line;
                  } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY3 + "=")) {
                      mParsedWepKeyLines[3] = line;
                  } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY_IDX + "=")) {
                      mParsedWepTxKeyIdxLine = line;
                  } else if (line.startsWith(SUPPLICANT_KEY_ID_STR + "=")) {
                      mParsedIdStrLine = line;
                  }
              }
  
              /**
               * Create WifiConfiguration object from the parsed data for this network.
               */
              public WifiConfiguration createWifiConfiguration() {
                  if (mParsedSSIDLine == null) {
                      // No SSID => malformed network definition
                      return null;
                  }
                  WifiConfiguration configuration = new WifiConfiguration();
                  configuration.SSID = mParsedSSIDLine.substring(mParsedSSIDLine.indexOf('=') + 1);
  
                  if (mParsedHiddenLine != null) {
                      // Can't use Boolean.valueOf() because it works only for true/false.
                      configuration.hiddenSSID =
                              Integer.parseInt(mParsedHiddenLine.substring(
                                      mParsedHiddenLine.indexOf('=') + 1)) != 0;
                  }
                  if (mParsedKeyMgmtLine == null) {
                      // no key_mgmt line specified; this is defined as equivalent to
                      // "WPA-PSK WPA-EAP".
                      configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
                      configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
                  } else {
                      // Need to parse the mParsedKeyMgmtLine line
                      final String bareKeyMgmt =
                              mParsedKeyMgmtLine.substring(mParsedKeyMgmtLine.indexOf('=') + 1);
                      String[] typeStrings = bareKeyMgmt.split("\\s+");
  
                      // Parse out all the key management regimes permitted for this network.
                      // The literal strings here are the standard values permitted in
                      // wpa_supplicant.conf.
                      for (int i = 0; i < typeStrings.length; i++) {
                          final String ktype = typeStrings[i];
                          if (ktype.equals("NONE")) {
                              configuration.allowedKeyManagement.set(
                                      WifiConfiguration.KeyMgmt.NONE);
                          } else if (ktype.equals("WPA-PSK")) {
                              configuration.allowedKeyManagement.set(
                                      WifiConfiguration.KeyMgmt.WPA_PSK);
                          } else if (ktype.equals("WPA-EAP")) {
                              configuration.allowedKeyManagement.set(
                                      WifiConfiguration.KeyMgmt.WPA_EAP);
                          } else if (ktype.equals("IEEE8021X")) {
                              configuration.allowedKeyManagement.set(
                                      WifiConfiguration.KeyMgmt.IEEE8021X);
                          }
                      }
                  }
                  if (mParsedPskLine != null) {
                      configuration.preSharedKey =
                              mParsedPskLine.substring(mParsedPskLine.indexOf('=') + 1);
                  }
                  if (mParsedWepKeyLines[0] != null) {
                      configuration.wepKeys[0] =
                              mParsedWepKeyLines[0].substring(mParsedWepKeyLines[0].indexOf('=') + 1);
                  }
                  if (mParsedWepKeyLines[1] != null) {
                      configuration.wepKeys[1] =
                              mParsedWepKeyLines[1].substring(mParsedWepKeyLines[1].indexOf('=') + 1);
                  }
                  if (mParsedWepKeyLines[2] != null) {
                      configuration.wepKeys[2] =
                              mParsedWepKeyLines[2].substring(mParsedWepKeyLines[2].indexOf('=') + 1);
                  }
                  if (mParsedWepKeyLines[3] != null) {
                      configuration.wepKeys[3] =
                              mParsedWepKeyLines[3].substring(mParsedWepKeyLines[3].indexOf('=') + 1);
                  }
                  if (mParsedWepTxKeyIdxLine != null) {
                      configuration.wepTxKeyIndex =
                              Integer.valueOf(mParsedWepTxKeyIdxLine.substring(
                                      mParsedWepTxKeyIdxLine.indexOf('=') + 1));
                  }
                  if (mParsedIdStrLine != null) {
                      String idString =
                              mParsedIdStrLine.substring(mParsedIdStrLine.indexOf('=') + 1);
                      if (idString != null) {
                          Map<String, String> extras =
                                  SupplicantStaNetworkHal.parseNetworkExtra(
                                          NativeUtil.removeEnclosingQuotes(idString));
                          if (extras == null) {
                              Log.e(TAG, "Error parsing network extras, ignoring network.");
                              return null;
                          }
                          String configKey = extras.get(
                                  SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY);
                          // No ConfigKey was passed but we need it for validating the parsed
                          // network so we stop the restore.
                          if (configKey == null) {
                              Log.e(TAG, "Configuration key was not passed, ignoring network.");
                              return null;
                          }
                          if (!configKey.equals(configuration.configKey())) {
                              // ConfigKey mismatches are expected for private networks because the
                              // UID is not preserved across backup/restore.
                              Log.w(TAG, "Configuration key does not match. Retrieved: " + configKey
                                      + ", Calculated: " + configuration.configKey());
                          }
                          // For wpa_supplicant backup data, parse out the creatorUid to ensure that
                          // these networks were created by system apps.
                          int creatorUid =
                                  Integer.parseInt(extras.get(
                                          SupplicantStaNetworkHal.ID_STRING_KEY_CREATOR_UID));
                          if (creatorUid >= Process.FIRST_APPLICATION_UID) {
                              Log.d(TAG, "Ignoring network from non-system app: "
                                      + configuration.configKey());
                              return null;
                          }
                      }
                  }
                  return configuration;
              }
          }
  
          /**
           * Ingest multiple wifi config fragments from wpa_supplicant.conf, looking for network={}
           * blocks and eliminating duplicates
           */
          static class SupplicantNetworks {
              final ArrayList<SupplicantNetwork> mNetworks = new ArrayList<>(8);
  
              /**
               * Parse the wpa_supplicant.conf file stream and add networks.
               */
              public void readNetworksFromStream(BufferedReader in) {
                  try {
                      String line;
                      while (in.ready()) {
                          line = in.readLine();
                          if (line != null) {
                              if (line.startsWith("network")) {
                                  SupplicantNetwork net = SupplicantNetwork.readNetworkFromStream(in);
  
                                  // An IOException occurred while trying to read the network.
                                  if (net == null) {
                                      Log.e(TAG, "Error while parsing the network.");
                                      continue;
                                  }
  
                                  // Networks that use certificates for authentication can't be
                                  // restored because the certificates they need don't get restored
                                  // (because they are stored in keystore, and can't be restored).
                                  // Similarly, omit EAP network definitions to avoid propagating
                                  // controlled enterprise network definitions.
                                  if (net.isEap || net.certUsed) {
                                      Log.d(TAG, "Skipping enterprise network for restore: "
                                              + net.mParsedSSIDLine + " / " + net.mParsedKeyMgmtLine);
                                      continue;
                                  }
                                  mNetworks.add(net);
                              }
                          }
                      }
                  } catch (IOException e) {
                      // whatever happened, we're done now
                  }
              }
  
              /**
               * Retrieve a list of WifiConfiguration objects parsed from wpa_supplicant.conf
               */
              public List<WifiConfiguration> retrieveWifiConfigurations() {
                  ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>();
                  for (SupplicantNetwork net : mNetworks) {
                      try {
                          WifiConfiguration wifiConfiguration = net.createWifiConfiguration();
                          if (wifiConfiguration != null) {
                              Log.v(TAG, "Parsed Configuration: " + wifiConfiguration.configKey());
                              wifiConfigurations.add(wifiConfiguration);
                          }
                      } catch (NumberFormatException e) {
                          // Occurs if we are unable to parse the hidden SSID, WEP Key index or
                          // creator UID.
                          Log.e(TAG, "Error parsing wifi configuration: " + e);
                          return null;
                      }
                  }
                  return wifiConfigurations;
              }
          }
      }
  }
  