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.net.wifi.ParcelUtil;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.text.TextUtils;
  import android.util.Base64;
  import android.util.Log;
  
  import java.nio.charset.StandardCharsets;
  import java.security.cert.X509Certificate;
  import java.util.Arrays;
  import java.util.Objects;
  
  /**
   * Class representing configuration parameters for subscription or policy update in
   * PerProviderSubscription (PPS) Management Object (MO) tree.  This is used by both
   * PerProviderSubscription/Policy/PolicyUpdate and PerProviderSubscription/SubscriptionUpdate
   * subtree.
   *
   * 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 UpdateParameter implements Parcelable {
      private static final String TAG = "UpdateParameter";
  
      /**
       * Value indicating policy update is not applicable.  Thus, never check with policy server
       * for updates.
       */
      public static final long UPDATE_CHECK_INTERVAL_NEVER = 0xFFFFFFFFL;
  
      /**
       * Valid string for UpdateMethod.
       */
      public static final String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
      public static final String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
  
      /**
       * Valid string for Restriction.
       */
      public static final String UPDATE_RESTRICTION_HOMESP = "HomeSP";
      public static final String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
      public static final String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
  
      /**
       * Maximum bytes for URI string.
       */
      private static final int MAX_URI_BYTES = 1023;
  
      /**
       * Maximum bytes for URI string.
       */
      private static final int MAX_URL_BYTES = 1023;
  
      /**
       * Maximum bytes for username.
       */
      private static final int MAX_USERNAME_BYTES = 63;
  
      /**
       * Maximum bytes for password.
       */
      private static final int MAX_PASSWORD_BYTES = 255;
  
      /**
       * Number of bytes for certificate SHA-256 fingerprint byte array.
       */
      private static final int CERTIFICATE_SHA256_BYTES = 32;
  
      /**
       * This specifies how often the mobile device shall check with policy server for updates.
       *
       * Using Long.MIN_VALUE to indicate unset value.
       */
      private long mUpdateIntervalInMinutes = Long.MIN_VALUE;
      public void setUpdateIntervalInMinutes(long updateIntervalInMinutes) {
          mUpdateIntervalInMinutes = updateIntervalInMinutes;
      }
      public long getUpdateIntervalInMinutes() {
          return mUpdateIntervalInMinutes;
      }
  
      /**
       * The method used to update the policy.  Permitted values are "OMA-DM-ClientInitiated"
       * and "SPP-ClientInitiated".
       */
      private String mUpdateMethod = null;
      public void setUpdateMethod(String updateMethod) {
          mUpdateMethod = updateMethod;
      }
      public String getUpdateMethod() {
          return mUpdateMethod;
      }
  
      /**
       * This specifies the hotspots at which the subscription update is permitted.  Permitted
       * values are "HomeSP", "RoamingPartner", or "Unrestricted";
       */
      private String mRestriction = null;
      public void setRestriction(String restriction) {
          mRestriction = restriction;
      }
      public String getRestriction() {
          return mRestriction;
      }
  
      /**
       * The URI of the update server.
       */
      private String mServerUri = null;
      public void setServerUri(String serverUri) {
          mServerUri = serverUri;
      }
      public String getServerUri() {
          return mServerUri;
      }
  
      /**
       * Username used to authenticate with the policy server.
       */
      private String mUsername = null;
      public void setUsername(String username) {
          mUsername = username;
      }
      public String getUsername() {
          return mUsername;
      }
  
      /**
       * Base64 encoded password used to authenticate with the policy server.
       */
      private String mBase64EncodedPassword = null;
      public void setBase64EncodedPassword(String password) {
          mBase64EncodedPassword = password;
      }
      public String getBase64EncodedPassword() {
          return mBase64EncodedPassword;
      }
  
      /**
       * HTTPS URL for retrieving certificate for trust root.  The trust root is used to validate
       * policy server's identity.
       */
      private String mTrustRootCertUrl = null;
      public void setTrustRootCertUrl(String trustRootCertUrl) {
          mTrustRootCertUrl = trustRootCertUrl;
      }
      public String getTrustRootCertUrl() {
          return mTrustRootCertUrl;
      }
  
      /**
       * SHA-256 fingerprint of the certificate located at {@code mTrustRootCertUrl}
       */
      private byte[] mTrustRootCertSha256Fingerprint = null;
      public void setTrustRootCertSha256Fingerprint(byte[] fingerprint) {
          mTrustRootCertSha256Fingerprint = fingerprint;
      }
      public byte[] getTrustRootCertSha256Fingerprint() {
          return mTrustRootCertSha256Fingerprint;
      }
  
      /**
       * CA (Certificate Authority) X509 certificates.
       */
      private X509Certificate mCaCertificate;
  
      /**
       * Set the CA (Certification Authority) certificate associated with Policy/Subscription update.
       *
       * @param caCertificate The CA certificate to set
       * @hide
       */
      public void setCaCertificate(X509Certificate caCertificate) {
          mCaCertificate = caCertificate;
      }
  
      /**
       * Get the CA (Certification Authority) certificate associated with Policy/Subscription update.
       *
       * @return CA certificate associated and {@code null} if certificate is not set.
       * @hide
       */
      public X509Certificate getCaCertificate() {
          return mCaCertificate;
      }
  
      /**
       * Constructor for creating Policy with default values.
       */
      public UpdateParameter() {}
  
      /**
       * Copy constructor.
       *
       * @param source The source to copy from
       */
      public UpdateParameter(UpdateParameter source) {
          if (source == null) {
              return;
          }
          mUpdateIntervalInMinutes = source.mUpdateIntervalInMinutes;
          mUpdateMethod = source.mUpdateMethod;
          mRestriction = source.mRestriction;
          mServerUri = source.mServerUri;
          mUsername = source.mUsername;
          mBase64EncodedPassword = source.mBase64EncodedPassword;
          mTrustRootCertUrl = source.mTrustRootCertUrl;
          if (source.mTrustRootCertSha256Fingerprint != null) {
              mTrustRootCertSha256Fingerprint = Arrays.copyOf(source.mTrustRootCertSha256Fingerprint,
                      source.mTrustRootCertSha256Fingerprint.length);
          }
          mCaCertificate = source.mCaCertificate;
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeLong(mUpdateIntervalInMinutes);
          dest.writeString(mUpdateMethod);
          dest.writeString(mRestriction);
          dest.writeString(mServerUri);
          dest.writeString(mUsername);
          dest.writeString(mBase64EncodedPassword);
          dest.writeString(mTrustRootCertUrl);
          dest.writeByteArray(mTrustRootCertSha256Fingerprint);
          ParcelUtil.writeCertificate(dest, mCaCertificate);
      }
  
      @Override
      public boolean equals(Object thatObject) {
          if (this == thatObject) {
              return true;
          }
          if (!(thatObject instanceof UpdateParameter)) {
              return false;
          }
          UpdateParameter that = (UpdateParameter) thatObject;
  
          return mUpdateIntervalInMinutes == that.mUpdateIntervalInMinutes
                  && TextUtils.equals(mUpdateMethod, that.mUpdateMethod)
                  && TextUtils.equals(mRestriction, that.mRestriction)
                  && TextUtils.equals(mServerUri, that.mServerUri)
                  && TextUtils.equals(mUsername, that.mUsername)
                  && TextUtils.equals(mBase64EncodedPassword, that.mBase64EncodedPassword)
                  && TextUtils.equals(mTrustRootCertUrl, that.mTrustRootCertUrl)
                  && Arrays.equals(mTrustRootCertSha256Fingerprint,
                  that.mTrustRootCertSha256Fingerprint)
                  && Credential.isX509CertificateEquals(mCaCertificate, that.mCaCertificate);
      }
  
      @Override
      public int hashCode() {
          return Objects.hash(mUpdateIntervalInMinutes, mUpdateMethod, mRestriction, mServerUri,
                  mUsername, mBase64EncodedPassword, mTrustRootCertUrl,
                  Arrays.hashCode(mTrustRootCertSha256Fingerprint), mCaCertificate);
      }
  
      @Override
      public String toString() {
          StringBuilder builder = new StringBuilder();
          builder.append("UpdateInterval: ").append(mUpdateIntervalInMinutes).append("\n");
          builder.append("UpdateMethod: ").append(mUpdateMethod).append("\n");
          builder.append("Restriction: ").append(mRestriction).append("\n");
          builder.append("ServerURI: ").append(mServerUri).append("\n");
          builder.append("Username: ").append(mUsername).append("\n");
          builder.append("TrustRootCertURL: ").append(mTrustRootCertUrl).append("\n");
          return builder.toString();
      }
  
      /**
       * Validate UpdateParameter data.
       *
       * @return true on success
       * @hide
       */
      public boolean validate() {
          if (mUpdateIntervalInMinutes == Long.MIN_VALUE) {
              Log.d(TAG, "Update interval not specified");
              return false;
          }
          // Update not applicable.
          if (mUpdateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) {
              return true;
          }
  
          if (!TextUtils.equals(mUpdateMethod, UPDATE_METHOD_OMADM)
                  && !TextUtils.equals(mUpdateMethod, UPDATE_METHOD_SSP)) {
              Log.d(TAG, "Unknown update method: " + mUpdateMethod);
              return false;
          }
  
          if (!TextUtils.equals(mRestriction, UPDATE_RESTRICTION_HOMESP)
                  && !TextUtils.equals(mRestriction, UPDATE_RESTRICTION_ROAMING_PARTNER)
                  && !TextUtils.equals(mRestriction, UPDATE_RESTRICTION_UNRESTRICTED)) {
              Log.d(TAG, "Unknown restriction: " + mRestriction);
              return false;
          }
  
          if (TextUtils.isEmpty(mServerUri)) {
              Log.d(TAG, "Missing update server URI");
              return false;
          }
          if (mServerUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) {
              Log.d(TAG, "URI bytes exceeded the max: "
                      + mServerUri.getBytes(StandardCharsets.UTF_8).length);
              return false;
          }
  
          if (TextUtils.isEmpty(mUsername)) {
              Log.d(TAG, "Missing username");
              return false;
          }
          if (mUsername.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
              Log.d(TAG, "Username bytes exceeded the max: "
                      + mUsername.getBytes(StandardCharsets.UTF_8).length);
              return false;
          }
  
          if (TextUtils.isEmpty(mBase64EncodedPassword)) {
              Log.d(TAG, "Missing username");
              return false;
          }
          if (mBase64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
              Log.d(TAG, "Password bytes exceeded the max: "
                      + mBase64EncodedPassword.getBytes(StandardCharsets.UTF_8).length);
              return false;
          }
          try {
              Base64.decode(mBase64EncodedPassword, Base64.DEFAULT);
          } catch (IllegalArgumentException e) {
              Log.d(TAG, "Invalid encoding for password: " + mBase64EncodedPassword);
              return false;
          }
  
          if (TextUtils.isEmpty(mTrustRootCertUrl)) {
              Log.d(TAG, "Missing trust root certificate URL");
              return false;
          }
          if (mTrustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
              Log.d(TAG, "Trust root cert URL bytes exceeded the max: "
                      + mTrustRootCertUrl.getBytes(StandardCharsets.UTF_8).length);
              return false;
          }
  
          if (mTrustRootCertSha256Fingerprint == null) {
              Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint");
              return false;
          }
          if (mTrustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) {
              Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
                      + mTrustRootCertSha256Fingerprint.length);
              return false;
          }
          return true;
      }
  
      public static final @android.annotation.NonNull Creator<UpdateParameter> CREATOR =
          new Creator<UpdateParameter>() {
              @Override
              public UpdateParameter createFromParcel(Parcel in) {
                  UpdateParameter updateParam = new UpdateParameter();
                  updateParam.setUpdateIntervalInMinutes(in.readLong());
                  updateParam.setUpdateMethod(in.readString());
                  updateParam.setRestriction(in.readString());
                  updateParam.setServerUri(in.readString());
                  updateParam.setUsername(in.readString());
                  updateParam.setBase64EncodedPassword(in.readString());
                  updateParam.setTrustRootCertUrl(in.readString());
                  updateParam.setTrustRootCertSha256Fingerprint(in.createByteArray());
                  updateParam.setCaCertificate(ParcelUtil.readCertificate(in));
                  return updateParam;
              }
  
              @Override
              public UpdateParameter[] newArray(int size) {
                  return new UpdateParameter[size];
              }
          };
  }
  