content: /**
   * Copyright (c) 2017, The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.net.wifi.hotspot2.pps;
  
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.text.TextUtils;
  import android.util.Log;
  
  import java.nio.charset.StandardCharsets;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  
  /**
   * Class representing Policy subtree in PerProviderSubscription (PPS)
   * Management Object (MO) tree.
   *
   * The Policy specifies additional criteria for Passpoint network selections, such as preferred
   * roaming partner, minimum backhaul bandwidth, and etc. It also provides the meta data for
   * updating the policy.
   *
   * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
   * Release 2 Technical Specification.
   *
   * @hide
   */
  public final class Policy implements Parcelable {
      private static final String TAG = "Policy";
  
      /**
       * Maximum number of SSIDs in the exclusion list.
       */
      private static final int MAX_EXCLUSION_SSIDS = 128;
  
      /**
       * Maximum byte for SSID.
       */
      private static final int MAX_SSID_BYTES = 32;
  
      /**
       * Maximum bytes for port string in {@link #requiredProtoPortMap}.
       */
      private static final int MAX_PORT_STRING_BYTES = 64;
  
      /**
       * Integer value used for indicating null value in the Parcel.
       */
      private static final int NULL_VALUE = -1;
  
      /**
       * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
       * selecting a network from home providers.
       *
       * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
       * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
       *
       * Using Long.MIN_VALUE to indicate unset value.
       */
      private long mMinHomeDownlinkBandwidth = Long.MIN_VALUE;
      public void setMinHomeDownlinkBandwidth(long minHomeDownlinkBandwidth) {
          mMinHomeDownlinkBandwidth = minHomeDownlinkBandwidth;
      }
      public long getMinHomeDownlinkBandwidth() {
          return mMinHomeDownlinkBandwidth;
      }
      private long mMinHomeUplinkBandwidth = Long.MIN_VALUE;
      public void setMinHomeUplinkBandwidth(long minHomeUplinkBandwidth) {
          mMinHomeUplinkBandwidth = minHomeUplinkBandwidth;
      }
      public long getMinHomeUplinkBandwidth() {
          return mMinHomeUplinkBandwidth;
      }
  
      /**
       * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
       * selecting a network from roaming providers.
       *
       * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
       * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
       *
       * Using Long.MIN_VALUE to indicate unset value.
       */
      private long mMinRoamingDownlinkBandwidth = Long.MIN_VALUE;
      public void setMinRoamingDownlinkBandwidth(long minRoamingDownlinkBandwidth) {
          mMinRoamingDownlinkBandwidth = minRoamingDownlinkBandwidth;
      }
      public long getMinRoamingDownlinkBandwidth() {
          return mMinRoamingDownlinkBandwidth;
      }
      private long mMinRoamingUplinkBandwidth = Long.MIN_VALUE;
      public void setMinRoamingUplinkBandwidth(long minRoamingUplinkBandwidth) {
          mMinRoamingUplinkBandwidth = minRoamingUplinkBandwidth;
      }
      public long getMinRoamingUplinkBandwidth() {
          return mMinRoamingUplinkBandwidth;
      }
  
      /**
       * List of SSIDs that are not preferred by the Home SP.
       */
      private String[] mExcludedSsidList = null;
      public void setExcludedSsidList(String[] excludedSsidList) {
          mExcludedSsidList = excludedSsidList;
      }
      public String[] getExcludedSsidList() {
          return mExcludedSsidList;
      }
  
      /**
       * List of IP protocol and port number required by one or more operator supported application.
       * The port string contained one or more port numbers delimited by ",".
       */
      private Map<Integer, String> mRequiredProtoPortMap = null;
      public void setRequiredProtoPortMap(Map<Integer, String> requiredProtoPortMap) {
          mRequiredProtoPortMap = requiredProtoPortMap;
      }
      public Map<Integer, String> getRequiredProtoPortMap() {
          return mRequiredProtoPortMap;
      }
  
      /**
       * This specifies the maximum acceptable BSS load policy.  This is used to prevent device
       * from joining an AP whose channel is overly congested with traffic.
       * Using Integer.MIN_VALUE to indicate unset value.
       */
      private int mMaximumBssLoadValue = Integer.MIN_VALUE;
      public void setMaximumBssLoadValue(int maximumBssLoadValue) {
          mMaximumBssLoadValue = maximumBssLoadValue;
      }
      public int getMaximumBssLoadValue() {
          return mMaximumBssLoadValue;
      }
  
      /**
       * Policy associated with a roaming provider.  This specifies a priority associated
       * with a roaming provider for given list of countries.
       *
       * Contains field under PerProviderSubscription/Policy/PreferredRoamingPartnerList.
       */
      public static final class RoamingPartner implements Parcelable {
          /**
           * FQDN of the roaming partner.
           */
          private String mFqdn = null;
          public void setFqdn(String fqdn) {
              mFqdn = fqdn;
          }
          public String getFqdn() {
              return mFqdn;
          }
  
          /**
           * Flag indicating the exact match of FQDN is required for FQDN matching.
           *
           * When this flag is set to false, sub-domain matching is used.  For example, when
           * {@link #fqdn} s set to "example.com", "host.example.com" would be a match.
           */
          private boolean mFqdnExactMatch = false;
          public void setFqdnExactMatch(boolean fqdnExactMatch) {
              mFqdnExactMatch = fqdnExactMatch;
          }
          public boolean getFqdnExactMatch() {
              return mFqdnExactMatch;
          }
  
          /**
           * Priority associated with this roaming partner policy.
           * Using Integer.MIN_VALUE to indicate unset value.
           */
          private int mPriority = Integer.MIN_VALUE;
          public void setPriority(int priority) {
              mPriority = priority;
          }
          public int getPriority() {
              return mPriority;
          }
  
          /**
           * A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
           * character country strings or the country-independent value, "*".
           */
          private String mCountries = null;
          public void setCountries(String countries) {
              mCountries = countries;
          }
          public String getCountries() {
              return mCountries;
          }
  
          public RoamingPartner() {}
  
          public RoamingPartner(RoamingPartner source) {
              if (source != null) {
                  mFqdn = source.mFqdn;
                  mFqdnExactMatch = source.mFqdnExactMatch;
                  mPriority = source.mPriority;
                  mCountries = source.mCountries;
              }
          }
  
          @Override
          public int describeContents() {
              return 0;
          }
  
          @Override
          public void writeToParcel(Parcel dest, int flags) {
              dest.writeString(mFqdn);
              dest.writeInt(mFqdnExactMatch ? 1 : 0);
              dest.writeInt(mPriority);
              dest.writeString(mCountries);
          }
  
          @Override
          public boolean equals(Object thatObject) {
              if (this == thatObject) {
                  return true;
              }
              if (!(thatObject instanceof RoamingPartner)) {
                  return false;
              }
  
              RoamingPartner that = (RoamingPartner) thatObject;
              return TextUtils.equals(mFqdn, that.mFqdn)
                      && mFqdnExactMatch == that.mFqdnExactMatch
                      && mPriority == that.mPriority
                      && TextUtils.equals(mCountries, that.mCountries);
          }
  
          @Override
          public int hashCode() {
              return Objects.hash(mFqdn, mFqdnExactMatch, mPriority, mCountries);
          }
  
          @Override
          public String toString() {
              StringBuilder builder = new StringBuilder();
              builder.append("FQDN: ").append(mFqdn).append("\n");
              builder.append("ExactMatch: ").append("mFqdnExactMatch").append("\n");
              builder.append("Priority: ").append(mPriority).append("\n");
              builder.append("Countries: ").append(mCountries).append("\n");
              return builder.toString();
          }
  
          /**
           * Validate RoamingParnter data.
           *
           * @return true on success
           * @hide
           */
          public boolean validate() {
              if (TextUtils.isEmpty(mFqdn)) {
                  Log.d(TAG, "Missing FQDN");
                  return false;
              }
              if (TextUtils.isEmpty(mCountries)) {
                  Log.d(TAG, "Missing countries");
                  return false;
              }
              return true;
          }
  
          public static final @android.annotation.NonNull Creator<RoamingPartner> CREATOR =
              new Creator<RoamingPartner>() {
                  @Override
                  public RoamingPartner createFromParcel(Parcel in) {
                      RoamingPartner roamingPartner = new RoamingPartner();
                      roamingPartner.setFqdn(in.readString());
                      roamingPartner.setFqdnExactMatch(in.readInt() != 0);
                      roamingPartner.setPriority(in.readInt());
                      roamingPartner.setCountries(in.readString());
                      return roamingPartner;
                  }
  
                  @Override
                  public RoamingPartner[] newArray(int size) {
                      return new RoamingPartner[size];
                  }
              };
      }
      private List<RoamingPartner> mPreferredRoamingPartnerList = null;
      public void setPreferredRoamingPartnerList(List<RoamingPartner> partnerList) {
          mPreferredRoamingPartnerList = partnerList;
      }
      public List<RoamingPartner> getPreferredRoamingPartnerList() {
          return mPreferredRoamingPartnerList;
      }
  
      /**
       * Meta data used for policy update.
       */
      private UpdateParameter mPolicyUpdate = null;
      public void setPolicyUpdate(UpdateParameter policyUpdate) {
          mPolicyUpdate = policyUpdate;
      }
      public UpdateParameter getPolicyUpdate() {
          return mPolicyUpdate;
      }
  
      /**
       * Constructor for creating Policy with default values.
       */
      public Policy() {}
  
      /**
       * Copy constructor.
       *
       * @param source The source to copy from
       */
      public Policy(Policy source) {
          if (source == null) {
              return;
          }
          mMinHomeDownlinkBandwidth = source.mMinHomeDownlinkBandwidth;
          mMinHomeUplinkBandwidth = source.mMinHomeUplinkBandwidth;
          mMinRoamingDownlinkBandwidth = source.mMinRoamingDownlinkBandwidth;
          mMinRoamingUplinkBandwidth = source.mMinRoamingUplinkBandwidth;
          mMaximumBssLoadValue = source.mMaximumBssLoadValue;
          if (source.mExcludedSsidList != null) {
              mExcludedSsidList = Arrays.copyOf(source.mExcludedSsidList,
                      source.mExcludedSsidList.length);
          }
          if (source.mRequiredProtoPortMap != null) {
              mRequiredProtoPortMap = Collections.unmodifiableMap(source.mRequiredProtoPortMap);
          }
          if (source.mPreferredRoamingPartnerList != null) {
              mPreferredRoamingPartnerList = Collections.unmodifiableList(
                      source.mPreferredRoamingPartnerList);
          }
          if (source.mPolicyUpdate != null) {
              mPolicyUpdate = new UpdateParameter(source.mPolicyUpdate);
          }
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeLong(mMinHomeDownlinkBandwidth);
          dest.writeLong(mMinHomeUplinkBandwidth);
          dest.writeLong(mMinRoamingDownlinkBandwidth);
          dest.writeLong(mMinRoamingUplinkBandwidth);
          dest.writeStringArray(mExcludedSsidList);
          writeProtoPortMap(dest, mRequiredProtoPortMap);
          dest.writeInt(mMaximumBssLoadValue);
          writeRoamingPartnerList(dest, flags, mPreferredRoamingPartnerList);
          dest.writeParcelable(mPolicyUpdate, flags);
      }
  
      @Override
      public boolean equals(Object thatObject) {
          if (this == thatObject) {
              return true;
          }
          if (!(thatObject instanceof Policy)) {
              return false;
          }
          Policy that = (Policy) thatObject;
  
          return mMinHomeDownlinkBandwidth == that.mMinHomeDownlinkBandwidth
                  && mMinHomeUplinkBandwidth == that.mMinHomeUplinkBandwidth
                  && mMinRoamingDownlinkBandwidth == that.mMinRoamingDownlinkBandwidth
                  && mMinRoamingUplinkBandwidth == that.mMinRoamingUplinkBandwidth
                  && Arrays.equals(mExcludedSsidList, that.mExcludedSsidList)
                  && (mRequiredProtoPortMap == null ? that.mRequiredProtoPortMap == null
                          : mRequiredProtoPortMap.equals(that.mRequiredProtoPortMap))
                  && mMaximumBssLoadValue == that.mMaximumBssLoadValue
                  && (mPreferredRoamingPartnerList == null
                          ? that.mPreferredRoamingPartnerList == null
                          : mPreferredRoamingPartnerList.equals(that.mPreferredRoamingPartnerList))
                  && (mPolicyUpdate == null ? that.mPolicyUpdate == null
                          : mPolicyUpdate.equals(that.mPolicyUpdate));
      }
  
      @Override
      public int hashCode() {
          return Objects.hash(mMinHomeDownlinkBandwidth, mMinHomeUplinkBandwidth,
                  mMinRoamingDownlinkBandwidth, mMinRoamingUplinkBandwidth, mExcludedSsidList,
                  mRequiredProtoPortMap, mMaximumBssLoadValue, mPreferredRoamingPartnerList,
                  mPolicyUpdate);
      }
  
      @Override
      public String toString() {
          StringBuilder builder = new StringBuilder();
          builder.append("MinHomeDownlinkBandwidth: ").append(mMinHomeDownlinkBandwidth)
                  .append("\n");
          builder.append("MinHomeUplinkBandwidth: ").append(mMinHomeUplinkBandwidth).append("\n");
          builder.append("MinRoamingDownlinkBandwidth: ").append(mMinRoamingDownlinkBandwidth)
                  .append("\n");
          builder.append("MinRoamingUplinkBandwidth: ").append(mMinRoamingUplinkBandwidth)
                  .append("\n");
          builder.append("ExcludedSSIDList: ").append(mExcludedSsidList).append("\n");
          builder.append("RequiredProtoPortMap: ").append(mRequiredProtoPortMap).append("\n");
          builder.append("MaximumBSSLoadValue: ").append(mMaximumBssLoadValue).append("\n");
          builder.append("PreferredRoamingPartnerList: ").append(mPreferredRoamingPartnerList)
                  .append("\n");
          if (mPolicyUpdate != null) {
              builder.append("PolicyUpdate Begin ---\n");
              builder.append(mPolicyUpdate);
              builder.append("PolicyUpdate End ---\n");
          }
          return builder.toString();
      }
  
      /**
       * Validate Policy data.
       *
       * @return true on success
       * @hide
       */
      public boolean validate() {
          if (mPolicyUpdate == null) {
              Log.d(TAG, "PolicyUpdate not specified");
              return false;
          }
          if (!mPolicyUpdate.validate()) {
              return false;
          }
  
          // Validate SSID exclusion list.
          if (mExcludedSsidList != null) {
              if (mExcludedSsidList.length > MAX_EXCLUSION_SSIDS) {
                  Log.d(TAG, "SSID exclusion list size exceeded the max: "
                          + mExcludedSsidList.length);
                  return false;
              }
              for (String ssid : mExcludedSsidList) {
                  if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
                      Log.d(TAG, "Invalid SSID: " + ssid);
                      return false;
                  }
              }
          }
          // Validate required protocol to port map.
          if (mRequiredProtoPortMap != null) {
              for (Map.Entry<Integer, String> entry : mRequiredProtoPortMap.entrySet()) {
                  String portNumber = entry.getValue();
                  if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) {
                      Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber);
                      return false;
                  }
              }
          }
          // Validate preferred roaming partner list.
          if (mPreferredRoamingPartnerList != null) {
              for (RoamingPartner partner : mPreferredRoamingPartnerList) {
                  if (!partner.validate()) {
                      return false;
                  }
              }
          }
          return true;
      }
  
      public static final @android.annotation.NonNull Creator<Policy> CREATOR =
          new Creator<Policy>() {
              @Override
              public Policy createFromParcel(Parcel in) {
                  Policy policy = new Policy();
                  policy.setMinHomeDownlinkBandwidth(in.readLong());
                  policy.setMinHomeUplinkBandwidth(in.readLong());
                  policy.setMinRoamingDownlinkBandwidth(in.readLong());
                  policy.setMinRoamingUplinkBandwidth(in.readLong());
                  policy.setExcludedSsidList(in.createStringArray());
                  policy.setRequiredProtoPortMap(readProtoPortMap(in));
                  policy.setMaximumBssLoadValue(in.readInt());
                  policy.setPreferredRoamingPartnerList(readRoamingPartnerList(in));
                  policy.setPolicyUpdate(in.readParcelable(null));
                  return policy;
              }
  
              @Override
              public Policy[] newArray(int size) {
                  return new Policy[size];
              }
  
              /**
               * Helper function for reading IP Protocol to Port Number map from a Parcel.
               *
               * @param in The Parcel to read from
               * @return Map of IP protocol to port number
               */
              private Map<Integer, String> readProtoPortMap(Parcel in) {
                  int size = in.readInt();
                  if (size == NULL_VALUE) {
                      return null;
                  }
                  Map<Integer, String> protoPortMap = new HashMap<>(size);
                  for (int i = 0; i < size; i++) {
                      int key = in.readInt();
                      String value = in.readString();
                      protoPortMap.put(key, value);
                  }
                  return protoPortMap;
              }
  
              /**
               * Helper function for reading roaming partner list from a Parcel.
               *
               * @param in The Parcel to read from
               * @return List of roaming partners
               */
              private List<RoamingPartner> readRoamingPartnerList(Parcel in) {
                  int size = in.readInt();
                  if (size == NULL_VALUE) {
                      return null;
                  }
                  List<RoamingPartner> partnerList = new ArrayList<>();
                  for (int i = 0; i < size; i++) {
                      partnerList.add(in.readParcelable(null));
                  }
                  return partnerList;
              }
  
          };
  
      /**
       * Helper function for writing IP Protocol to Port Number map to a Parcel.
       *
       * @param dest The Parcel to write to
       * @param protoPortMap The map to write
       */
      private static void writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap) {
          if (protoPortMap == null) {
              dest.writeInt(NULL_VALUE);
              return;
          }
          dest.writeInt(protoPortMap.size());
          for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
              dest.writeInt(entry.getKey());
              dest.writeString(entry.getValue());
          }
      }
  
      /**
       * Helper function for writing roaming partner list to a Parcel.
       *
       * @param dest The Parcel to write to
       * @param flags The flag about how the object should be written
       * @param partnerList The partner list to write
       */
      private static void writeRoamingPartnerList(Parcel dest, int flags,
              List<RoamingPartner> partnerList) {
          if (partnerList == null) {
              dest.writeInt(NULL_VALUE);
              return;
          }
          dest.writeInt(partnerList.size());
          for (RoamingPartner partner : partnerList) {
              dest.writeParcelable(partner, flags);
          }
      }
  }
  