content: /*
   * Copyright (C) 2019 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.ike.ikev2;
  
  import android.os.Looper;
  import android.os.Message;
  
  import com.android.ike.ikev2.IkeSessionStateMachine.IChildSessionCallback;
  import com.android.ike.ikev2.SaRecord.ChildSaRecord;
  import com.android.ike.ikev2.exceptions.IkeException;
  import com.android.ike.ikev2.message.IkePayload;
  import com.android.ike.ikev2.message.IkeSaPayload;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.util.State;
  import com.android.internal.util.StateMachine;
  
  import java.util.List;
  
  /**
   * ChildSessionStateMachine tracks states and manages exchanges of this Child Session.
   *
   * <p>ChildSessionStateMachine has two types of states. One type are states where there is no
   * ongoing procedure affecting Child Session (non-procedure state), including Initial, Closed, Idle
   * and Receiving. All other states are "procedure" states which are named as follows:
   *
   * <pre>
   * State Name = [Procedure Type] + [Exchange Initiator] + [Exchange Type].
   * - An IKE procedure consists of one or two IKE exchanges:
   *      Procedure Type = {CreateChild | DeleteChild | Info | RekeyChild | SimulRekeyChild}.
   * - Exchange Initiator indicates whether local or remote peer is the exchange initiator:
   *      Exchange Initiator = {Local | Remote}
   * - Exchange type defines the function of this exchange.
   *      Exchange Type = {Create | Delete}
   * </pre>
   */
  public class ChildSessionStateMachine extends StateMachine {
      private static final String TAG = "ChildSessionStateMachine";
  
      /** Receive request for negotiating first Child SA. */
      private static final int CMD_HANDLE_FIRST_CHILD_EXCHANGE = 1;
  
      private final ChildSessionOptions mChildSessionOptions;
  
      /** Package private */
      @VisibleForTesting ChildSaRecord mCurrentChildSaRecord;
  
      private final State mInitial = new Initial();
      private final State mClosed = new Closed();
      private final State mIdle = new Idle();
  
      /** Package private */
      ChildSessionStateMachine(String name, Looper looper, ChildSessionOptions sessionOptions) {
          super(name, looper);
  
          mChildSessionOptions = sessionOptions;
  
          addState(mInitial);
          addState(mClosed);
          addState(mIdle);
  
          setInitialState(mInitial);
      }
  
      private void validateCreateChildResp(
              List<IkePayload> reqPayloads, List<IkePayload> respPayloads) throws IkeException {
          // TODO: Validate SA reponse against request and set negotiated SA in mChildSessionOptions.
          return;
      }
  
      /**
       * Receive requesting and responding payloads for negotiating first Child SA.
       *
       * <p>This method is called synchronously from IkeStateMachine. It proxies the synchronous call
       * as an asynchronous job to the ChildStateMachine handler.
       *
       * @param reqPayloads SA negotiation related payloads in IKE_AUTH request.
       * @param respPayloads SA negotiation related payloads in IKE_AUTH response.
       * @param callback callback for notifying IkeSessionStateMachine the negotiation result.
       */
      public void handleFirstChildExchange(
              List<IkePayload> reqPayloads,
              List<IkePayload> respPayloads,
              IChildSessionCallback callback) {
          registerProvisionalChildSession(respPayloads, callback);
  
          sendMessage(
                  CMD_HANDLE_FIRST_CHILD_EXCHANGE,
                  new FirstChildNegotiationData(reqPayloads, respPayloads, callback));
      }
  
      /**
       * Register provisioning ChildSessionStateMachine in IChildSessionCallback
       *
       * <p>This method is for avoiding CHILD_SA_NOT_FOUND error in IkeSessionStateMachine when remote
       * peer sends request for delete/rekey this Child SA before ChildSessionStateMachine sends
       * FirstChildNegotiationData to itself.
       */
      private void registerProvisionalChildSession(
              List<IkePayload> respPayloads, IChildSessionCallback callback) {
          // When decoding responding IkeSaPayload in IkeSessionStateMachine, it is validated that
          // IkeSaPayload has exactly one IkeSaPayload.Proposal.
          IkeSaPayload saPayload = null;
          for (IkePayload payload : respPayloads) {
              if (payload.payloadType == IkePayload.PAYLOAD_TYPE_SA) {
                  saPayload = (IkeSaPayload) payload;
                  break;
              }
          }
          if (saPayload == null) {
              throw new IllegalArgumentException(
                      "Receive no SA payload for first Child SA negotiation.");
          }
          // IkeSaPayload.Proposal stores SPI in long type so as to be applied to both 8-byte IKE SPI
          // and 4-byte Child SPI. Here we cast the stored SPI to int to represent a Child SPI.
          int remoteGenSpi = (int) (saPayload.proposalList.get(0).spi);
          callback.onCreateChildSa(remoteGenSpi, this);
      }
  
      /**
       * FirstChildNegotiationData contains payloads for negotiating first Child SA in IKE_AUTH
       * request and IKE_AUTH response and callback to notify IkeSessionStateMachine the SA
       * negotiation result.
       */
      private static class FirstChildNegotiationData {
          public final List<IkePayload> requestPayloads;
          public final List<IkePayload> responsePayloads;
          public final IChildSessionCallback childCallback;
  
          FirstChildNegotiationData(
                  List<IkePayload> reqPayloads,
                  List<IkePayload> respPayloads,
                  IChildSessionCallback callback) {
              requestPayloads = reqPayloads;
              responsePayloads = respPayloads;
              childCallback = callback;
          }
      }
  
      /** Initial state of ChildSessionStateMachine. */
      class Initial extends State {
          @Override
          public boolean processMessage(Message message) {
              switch (message.what) {
                      // TODO: Handle local request for creating Child SA.
                  case CMD_HANDLE_FIRST_CHILD_EXCHANGE:
                      FirstChildNegotiationData childNegotiationData =
                              (FirstChildNegotiationData) message.obj;
                      try {
                          List<IkePayload> reqPayloads = childNegotiationData.requestPayloads;
                          List<IkePayload> respPayloads = childNegotiationData.responsePayloads;
                          validateCreateChildResp(reqPayloads, respPayloads);
  
                          mCurrentChildSaRecord =
                                  ChildSaRecord.makeChildSaRecord(reqPayloads, respPayloads);
                          // TODO: Add mCurrentChildSaRecord in mSpiToSaRecordMap.
                          transitionTo(mIdle);
                      } catch (IkeException e) {
                          // TODO: Unregister remotely generated SPI and handle Child SA negotiation
                          // failure.
                      }
                      return HANDLED;
                  default:
                      return NOT_HANDLED;
              }
          }
      }
  
      /**
       * Closed represents the state when this ChildSessionStateMachine is closed, and no further
       * actions can be performed on it.
       */
      class Closed extends State {
          // TODO: Implement it.
      }
  
      /**
       * Idle represents a state when there is no ongoing IKE exchange affecting established Child SA.
       */
      class Idle extends State {
          // TODO: Implement it.
      }
  
      // TODO: Add states to support creating additional Child SA, deleting Child SA and rekeying
      // Child SA.
  }
  