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.hotspot2.soap.command;
  
  import android.annotation.NonNull;
  import android.util.Log;
  
  import org.ksoap2.serialization.PropertyInfo;
  import org.ksoap2.serialization.SoapObject;
  
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Objects;
  
  /**
   * Commands that the mobile device is being requested to execute, which is defined in SPP
   * (Subscription Provisioning Protocol).
   *
   * For the details, refer to A.3.2 of Hotspot 2.0 rel2 technical specification.
   */
  public class SppCommand {
      private static final String TAG = "PasspointSppCommand";
      private int mSppCommandId;
      private int mExecCommandId = -1;
      private SppCommandData mCommandData;
  
      /**
       * Marker interface to indicate data used for a SPP(Subscription Provisioning Protocol) command
       */
      public interface SppCommandData {
  
      }
  
      /**
       * Commands embedded in sppPostDevDataResponse message for client to take an action.
       */
      public class CommandId {
          public static final int EXEC = 0;
          public static final int ADD_MO = 1;
          public static final int UPDATE_NODE = 2;
          public static final int NO_MO_UPDATE = 3;
      }
  
      private static final Map<String, Integer> sCommands = new HashMap<>();
      static {
          sCommands.put("exec", CommandId.EXEC);
          sCommands.put("addMO", CommandId.ADD_MO);
          sCommands.put("updateNode", CommandId.UPDATE_NODE);
          sCommands.put("noMOUpdate", CommandId.NO_MO_UPDATE);
      }
  
      /**
       * Execution types embedded in exec command for client to execute it.
       */
      public class ExecCommandId {
          public static final int BROWSER = 0;
          public static final int GET_CERT = 1;
          public static final int USE_CLIENT_CERT_TLS = 2;
          public static final int UPLOAD_MO = 3;
      }
  
      private static final Map<String, Integer> sExecs = new HashMap<>();
      static {
          sExecs.put("launchBrowserToURI", ExecCommandId.BROWSER);
          sExecs.put("getCertificate", ExecCommandId.GET_CERT);
          sExecs.put("useClientCertTLS", ExecCommandId.USE_CLIENT_CERT_TLS);
          sExecs.put("uploadMO", ExecCommandId.UPLOAD_MO);
      }
  
      private SppCommand(PropertyInfo soapResponse) throws IllegalArgumentException {
          if (!sCommands.containsKey(soapResponse.getName())) {
              throw new IllegalArgumentException("can't find the command: " + soapResponse.getName());
          }
          mSppCommandId = sCommands.get(soapResponse.getName());
  
          Log.i(TAG, "command name: " + soapResponse.getName());
  
          switch(mSppCommandId) {
              case CommandId.EXEC:
                  /*
                   * Receipt of this element by a mobile device causes the following command
                   * to be executed.
                   */
                  SoapObject subCommand = (SoapObject) soapResponse.getValue();
                  if (subCommand.getPropertyCount() != 1) {
                      throw new IllegalArgumentException(
                              "more than one child element found for exec command: "
                                      + subCommand.getPropertyCount());
                  }
  
                  PropertyInfo commandInfo = new PropertyInfo();
                  subCommand.getPropertyInfo(0, commandInfo);
                  if (!sExecs.containsKey(commandInfo.getName())) {
                      throw new IllegalArgumentException(
                              "Unrecognized exec command: " + commandInfo.getName());
                  }
                  mExecCommandId = sExecs.get(commandInfo.getName());
                  Log.i(TAG, "exec command: " +  commandInfo.getName());
  
                  switch (mExecCommandId) {
                      case ExecCommandId.BROWSER:
                          /*
                           * When the mobile device receives this command, it launches its default
                           * browser to the URI contained in this element. The URI must use HTTPS as
                           * the protocol and must contain a FQDN.
                           */
                          mCommandData = BrowserUri.createInstance(commandInfo);
                          break;
                      case ExecCommandId.GET_CERT: //fall-through
                      case ExecCommandId.UPLOAD_MO: //fall-through
                      case ExecCommandId.USE_CLIENT_CERT_TLS: //fall-through
                          /*
                           * Command to mobile to re-negotiate the TLS connection using a client
                           * certificate of the accepted type or Issuer to authenticate with the
                           * Subscription server.
                           */
                      default:
                          mCommandData = null;
                          break;
                  }
                  break;
              case CommandId.ADD_MO:
                  /*
                   * This command causes an management object in the mobile devices management tree
                   * at the specified location to be added.
                   * If there is already a management object at that location, the object is replaced.
                   */
                  mCommandData = PpsMoData.createInstance(soapResponse);
                  break;
              case CommandId.UPDATE_NODE:
                  /*
                   * This command causes the update of an interior node and its child nodes (if any)
                   * at the location specified in the management tree URI attribute. The content of
                   * this element is the MO node XML.
                   */
                  break;
              case CommandId.NO_MO_UPDATE:
                  /*
                   * This response is used when there is no command to be executed nor update of
                   * any MO required.
                   */
                  break;
              default:
                  mExecCommandId = -1;
                  mCommandData = null;
                  break;
          }
      }
  
      /**
       * Create an instance of {@link SppCommand}
       *
       * @param soapResponse SOAP Response received from server.
       * @return instance of {@link SppCommand}
       */
      public static SppCommand createInstance(@NonNull PropertyInfo soapResponse) {
          SppCommand sppCommand;
          try {
              sppCommand = new SppCommand(soapResponse);
          } catch (IllegalArgumentException e) {
              Log.e(TAG, "fails to create an instance: " + e);
              return null;
          }
          return sppCommand;
      }
  
      public int getSppCommandId() {
          return mSppCommandId;
      }
  
      public int getExecCommandId() {
          return mExecCommandId;
      }
  
      public SppCommandData getCommandData() {
          return mCommandData;
      }
  
      @Override
      public boolean equals(Object thatObject) {
          if (this == thatObject) return true;
          if (!(thatObject instanceof SppCommand)) return false;
          SppCommand that = (SppCommand) thatObject;
          return (mSppCommandId == that.getSppCommandId())
                  && (mExecCommandId == that.getExecCommandId())
                  && Objects.equals(mCommandData, that.getCommandData());
      }
  
      @Override
      public int hashCode() {
          return Objects.hash(mSppCommandId, mExecCommandId, mCommandData);
      }
  
      @Override
      public String toString() {
          return "SppCommand{"
                  + "mSppCommandId=" + mSppCommandId
                  + ", mExecCommandId=" + mExecCommandId
                  + ", mCommandData=" + mCommandData
                  + "}";
      }
  }
  