content: /*
   * Copyright (C) 2018 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.annotation.Nullable;
  import android.net.MacAddress;
  import android.util.Log;
  
  import com.android.server.wifi.WifiNetworkFactory.AccessPoint;
  import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
  import com.android.server.wifi.util.XmlUtil;
  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.IOException;
  import java.util.HashMap;
  import java.util.LinkedHashSet;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.Set;
  
  /**
   * This class performs serialization and parsing of XML data block that contain the list of WiFi
   * network request approvals.
   */
  public class NetworkRequestStoreData implements WifiConfigStore.StoreData {
      private static final String TAG = "NetworkRequestStoreData";
  
      private static final String XML_TAG_SECTION_HEADER_NETWORK_REQUEST_MAP =
              "NetworkRequestMap";
      private static final String XML_TAG_SECTION_HEADER_APPROVED_ACCESS_POINTS_PER_APP =
              "ApprovedAccessPointsPerApp";
      private static final String XML_TAG_REQUESTOR_PACKAGE_NAME = "RequestorPackageName";
      private static final String XML_TAG_SECTION_HEADER_ACCESS_POINT = "AccessPoint";
      private static final String XML_TAG_ACCESS_POINT_SSID = WifiConfigurationXmlUtil.XML_TAG_SSID;
      private static final String XML_TAG_ACCESS_POINT_BSSID = WifiConfigurationXmlUtil.XML_TAG_BSSID;
      private static final String XML_TAG_ACCESS_POINT_NETWORK_TYPE = "NetworkType";
  
      /**
       * Interface define the data source for the network requests store data.
       */
      public interface DataSource {
          /**
           * Retrieve the approved access points from the data source to serialize them to disk.
           *
           * @return Map of package name to a set of {@link AccessPoint}
           */
          Map<String, Set<AccessPoint>> toSerialize();
  
          /**
           * Set the approved access points in the data source after serializing them from disk.
           *
           * @param approvedAccessPoints Map of package name to {@link AccessPoint}
           */
          void fromDeserialized(Map<String, Set<AccessPoint>> approvedAccessPoints);
  
          /**
           * Clear internal data structure in preparation for user switch or initial store read.
           */
          void reset();
  
          /**
           * Indicates whether there is new data to serialize.
           */
          boolean hasNewDataToSerialize();
      }
  
      private final DataSource mDataSource;
  
      public NetworkRequestStoreData(DataSource dataSource) {
          mDataSource = dataSource;
      }
  
      @Override
      public void serializeData(XmlSerializer out,
              @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
              throws XmlPullParserException, IOException {
          serializeApprovedAccessPointsMap(out, mDataSource.toSerialize());
      }
  
      @Override
      public void deserializeData(XmlPullParser in, int outerTagDepth,
              @WifiConfigStore.Version int version,
              @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
              throws XmlPullParserException, IOException {
          // Ignore empty reads.
          if (in == null) {
              return;
          }
          mDataSource.fromDeserialized(parseApprovedAccessPointsMap(in, outerTagDepth));
      }
  
      @Override
      public void resetData() {
          mDataSource.reset();
      }
  
      @Override
      public boolean hasNewDataToSerialize() {
          return mDataSource.hasNewDataToSerialize();
      }
  
      @Override
      public String getName() {
          return XML_TAG_SECTION_HEADER_NETWORK_REQUEST_MAP;
      }
  
      @Override
      public @WifiConfigStore.StoreFileId int getStoreFileId() {
          return WifiConfigStore.STORE_FILE_USER_GENERAL;
      }
  
      /**
       * Serialize the map of package name to approved access points to an output stream in XML
       * format.
       *
       * @throws XmlPullParserException
       * @throws IOException
       */
      private void serializeApprovedAccessPointsMap(
              XmlSerializer out, final Map<String, Set<AccessPoint>> approvedAccessPointsMap)
              throws XmlPullParserException, IOException {
          if (approvedAccessPointsMap == null) {
              return;
          }
          for (Entry<String, Set<AccessPoint>> entry : approvedAccessPointsMap.entrySet()) {
              String packageName = entry.getKey();
              XmlUtil.writeNextSectionStart(out,
                      XML_TAG_SECTION_HEADER_APPROVED_ACCESS_POINTS_PER_APP);
              XmlUtil.writeNextValue(out, XML_TAG_REQUESTOR_PACKAGE_NAME, packageName);
              serializeApprovedAccessPoints(out, entry.getValue());
              XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_APPROVED_ACCESS_POINTS_PER_APP);
          }
      }
  
      /**
       * Serialize the set of approved access points to an output stream in XML format.
       *
       * @throws XmlPullParserException
       * @throws IOException
       */
      private void serializeApprovedAccessPoints(
              XmlSerializer out, final Set<AccessPoint> approvedAccessPoints)
              throws XmlPullParserException, IOException {
          for (AccessPoint approvedAccessPoint : approvedAccessPoints) {
              serializeApprovedAccessPoint(out, approvedAccessPoint);
          }
      }
  
      /**
       * Serialize a {@link AccessPoint} to an output stream in XML format.
       *
       * @throws XmlPullParserException
       * @throws IOException
       */
      private void serializeApprovedAccessPoint(XmlSerializer out,
                                                AccessPoint approvedAccessPoint)
              throws XmlPullParserException, IOException {
          XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_ACCESS_POINT);
          XmlUtil.writeNextValue(out, XML_TAG_ACCESS_POINT_SSID, approvedAccessPoint.ssid);
          XmlUtil.writeNextValue(out, XML_TAG_ACCESS_POINT_BSSID,
                  approvedAccessPoint.bssid.toString());
          XmlUtil.writeNextValue(out, XML_TAG_ACCESS_POINT_NETWORK_TYPE,
                  approvedAccessPoint.networkType);
          XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_ACCESS_POINT);
      }
  
      /**
       * Parse a map of package name to approved access point from an input stream in XML format.
       *
       * @throws XmlPullParserException
       * @throws IOException
       */
      private Map<String, Set<AccessPoint>> parseApprovedAccessPointsMap(XmlPullParser in,
                                                                         int outerTagDepth)
              throws XmlPullParserException, IOException {
          Map<String, Set<AccessPoint>> approvedAccessPointsMap = new HashMap<>();
          while (XmlUtil.gotoNextSectionWithNameOrEnd(
                  in, XML_TAG_SECTION_HEADER_APPROVED_ACCESS_POINTS_PER_APP, outerTagDepth)) {
              // Try/catch only runtime exceptions (like illegal args), any XML/IO exceptions are
              // fatal and should abort the entire loading process.
              try {
                  String packageName =
                          (String) XmlUtil.readNextValueWithName(in, XML_TAG_REQUESTOR_PACKAGE_NAME);
                  Set<AccessPoint> approvedAccessPoints =
                          parseApprovedAccessPoints(in, outerTagDepth + 1);
                  approvedAccessPointsMap.put(packageName, approvedAccessPoints);
              } catch (RuntimeException e) {
                  // Failed to parse this network, skip it.
                  Log.e(TAG, "Failed to parse network suggestion. Skipping...", e);
              }
          }
          return approvedAccessPointsMap;
      }
  
      /**
       * Parse a set of approved access points from an input stream in XML format.
       *
       * @throws XmlPullParserException
       * @throws IOException
       */
      private Set<AccessPoint> parseApprovedAccessPoints(XmlPullParser in, int outerTagDepth)
              throws XmlPullParserException, IOException {
          Set<AccessPoint> approvedAccessPoints = new LinkedHashSet<>();
          while (XmlUtil.gotoNextSectionWithNameOrEnd(
                  in, XML_TAG_SECTION_HEADER_ACCESS_POINT, outerTagDepth)) {
              // Try/catch only runtime exceptions (like illegal args), any XML/IO exceptions are
              // fatal and should abort the entire loading process.
              try {
                  AccessPoint approvedAccessPoint =
                          parseApprovedAccessPoint(in, outerTagDepth + 1);
                  approvedAccessPoints.add(approvedAccessPoint);
              } catch (RuntimeException e) {
                  // Failed to parse this network, skip it.
                  Log.e(TAG, "Failed to parse network suggestion. Skipping...", e);
              }
          }
          return approvedAccessPoints;
      }
  
      /**
       * Parse a {@link AccessPoint} from an input stream in XML format.
       *
       * @throws XmlPullParserException
       * @throws IOException
       */
      private AccessPoint parseApprovedAccessPoint(XmlPullParser in, int outerTagDepth)
              throws XmlPullParserException, IOException {
          String ssid = null;
          MacAddress bssid = null;
          int networkType = -1;
  
          // 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_ACCESS_POINT_SSID:
                      ssid = (String) value;
                      break;
                  case XML_TAG_ACCESS_POINT_BSSID:
                      bssid = MacAddress.fromString((String) value);
                      break;
                  case XML_TAG_ACCESS_POINT_NETWORK_TYPE:
                      networkType = (int) value;
                      break;
                  default:
                      throw new XmlPullParserException(
                              "Unknown value name found: " + valueName[0]);
              }
          }
          if (ssid == null) {
              throw new XmlPullParserException("XML parsing of ssid failed");
          }
          if (bssid == null) {
              throw new XmlPullParserException("XML parsing of bssid failed");
          }
          if (networkType == -1) {
              throw new XmlPullParserException("XML parsing of network type failed");
          }
          return new AccessPoint(ssid, bssid, networkType);
      }
  }
  
  