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.ike.ikev2.message;
  
  import static com.android.ike.ikev2.SaProposal.DhGroup;
  import static com.android.ike.ikev2.SaProposal.EncryptionAlgorithm;
  import static com.android.ike.ikev2.SaProposal.IntegrityAlgorithm;
  import static com.android.ike.ikev2.SaProposal.PseudorandomFunction;
  
  import android.annotation.IntDef;
  import android.util.ArraySet;
  import android.util.Pair;
  
  import com.android.ike.ikev2.SaProposal;
  import com.android.ike.ikev2.exceptions.IkeException;
  import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
  import com.android.ike.ikev2.exceptions.NoValidProposalChosenException;
  import com.android.internal.annotations.VisibleForTesting;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.nio.ByteBuffer;
  import java.util.ArrayList;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Objects;
  import java.util.Set;
  
  /**
   * IkeSaPayload represents a Security Association payload. It contains one or more {@link Proposal}.
   *
   * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key Exchange
   *     Protocol Version 2 (IKEv2)</a>
   */
  public final class IkeSaPayload extends IkePayload {
      public final boolean isSaResponse;
      public final List<Proposal> proposalList;
      /**
       * Construct an instance of IkeSaPayload for decoding an inbound packet.
       *
       * @param critical indicates if this payload is critical. Ignored in supported payload as
       *     instructed by the RFC 7296.
       * @param isResp indicates if this payload is in a response message.
       * @param payloadBody the encoded payload body in byte array.
       */
      IkeSaPayload(boolean critical, boolean isResp, byte[] payloadBody) throws IkeException {
          super(IkePayload.PAYLOAD_TYPE_SA, critical);
  
          ByteBuffer inputBuffer = ByteBuffer.wrap(payloadBody);
          proposalList = new LinkedList<>();
          while (inputBuffer.hasRemaining()) {
              Proposal proposal = Proposal.readFrom(inputBuffer);
              proposalList.add(proposal);
          }
  
          // An SA response must have exactly one SA proposal.
          if (isResp && proposalList.size() != 1) {
              throw new InvalidSyntaxException(
                      "Expected only one negotiated proposal from SA response: "
                              + "Multiple negotiated proposals found.");
          }
          isSaResponse = isResp;
      }
  
      /**
       * Construct an instance of IkeSaPayload for building outbound packet.
       *
       * <p>The length of spis must be the same as saProposals.
       *
       * @param isResp indicates if this payload is in a response message.
       * @param isIkeSa indicates if this payload is for IKE SA or Child SA
       * @param spiSize the size of attached SPIs.
       * @param spis the array of all attached SPIs.
       * @param saProposals the array of all SA Proposals.
       */
      public IkeSaPayload(
              boolean isResp, boolean isIkeSa, byte spiSize, long[] spis, SaProposal[] saProposals) {
          super(IkePayload.PAYLOAD_TYPE_SA, false);
  
          if (saProposals.length < 1
                  || isResp && (saProposals.length > 1)
                  || saProposals.length != spis.length) {
              throw new IllegalArgumentException("Invalid SA payload.");
          }
  
          // TODO: Check that saProposals.length <= 255 in IkeSessionOptions and ChildSessionOptions
          isSaResponse = isResp;
  
          proposalList = new ArrayList<Proposal>(saProposals.length);
          int protocolId = isIkeSa ? PROTOCOL_ID_IKE : PROTOCOL_ID_ESP;
          for (int i = 0; i < saProposals.length; i++) {
              // Proposal number must start from 1.
              Proposal proposal =
                      new Proposal(
                              (byte) (i + 1) /*proposal number*/,
                              protocolId,
                              spiSize,
                              spis[i],
                              saProposals[i],
                              false /*does not have unrecognized Transform*/);
              proposalList.add(proposal);
          }
      }
  
      /**
       * Construct an instance of IkeSaPayload for building outbound IKE initial setup request.
       *
       * <p>According to RFC 7296, for an initial IKE SA negotiation, no SPI is included in SA
       * Proposal. IKE library, as a client, only supports requesting this initial negotiation.
       *
       * @param saProposals the array of all SA Proposals.
       */
      public IkeSaPayload(SaProposal[] saProposals) {
          this(
                  false /*is request*/,
                  true /*is IKE SA*/,
                  (byte) 0,
                  new long[saProposals.length],
                  saProposals);
      }
  
      /**
       * Validate and return the negotiated SA proposal from the received SA payload.
       *
       * @param reqSaPayload SA payload from SA initiator to validate against.
       * @return the validated negotiated SA proposal.
       * @throws NoValidProposalChosenException if received SA proposal is invalid.
       */
      public SaProposal getVerifiedNegotiatedProposal(IkeSaPayload reqSaPayload)
              throws NoValidProposalChosenException {
          if (!isSaResponse) {
              throw new UnsupportedOperationException(
                      "Cannot get negotiated SA proposal from a request message.");
          }
  
          // If negotiated proposal has an unrecognized Transform, throw an exception.
          Proposal respProposal = proposalList.get(0);
          if (respProposal.hasUnrecognizedTransform) {
              throw new NoValidProposalChosenException(
                      "Negotiated proposal has unrecognized Transform.");
          }
  
          // In SA request payload, the first proposal MUST be 1, and subsequent proposals MUST be one
          // more than the previous proposal. In SA response payload, the negotiated proposal number
          // MUST match the selected proposal number in SA request Payload.
          int negotiatedProposalNum = respProposal.number;
          List<Proposal> reqProposalList = reqSaPayload.proposalList;
          if (negotiatedProposalNum < 1 || negotiatedProposalNum > reqProposalList.size()) {
              throw new NoValidProposalChosenException(
                      "Negotiated proposal has invalid proposal number.");
          }
  
          Proposal reqProposal = reqProposalList.get(negotiatedProposalNum - 1);
          if (!respProposal.isNegotiatedFrom(reqProposal)) {
              throw new NoValidProposalChosenException("Invalid negotiated proposal.");
          }
          return respProposal.saProposal;
      }
  
      @VisibleForTesting
      interface TransformDecoder {
          Transform[] decodeTransforms(int count, ByteBuffer inputBuffer) throws IkeException;
      }
  
      // TODO: Add another constructor for building outbound message.
  
      /**
       * Proposal represents a set contains cryptographic algorithms and key generating materials. It
       * contains multiple {@link Transform}.
       *
       * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.1">RFC 7296, Internet Key
       *     Exchange Protocol Version 2 (IKEv2)</a>
       *     <p>Proposals with an unrecognized Protocol ID, containing an unrecognized Transform Type
       *     or lacking a necessary Transform Type shall be ignored when processing a received SA
       *     Payload.
       */
      public static final class Proposal {
          private static final byte LAST_PROPOSAL = 0;
          private static final byte NOT_LAST_PROPOSAL = 2;
  
          private static final int PROPOSAL_RESERVED_FIELD_LEN = 1;
          private static final int PROPOSAL_HEADER_LEN = 8;
  
          @VisibleForTesting
          static TransformDecoder sTransformDecoder =
                  new TransformDecoder() {
                      @Override
                      public Transform[] decodeTransforms(int count, ByteBuffer inputBuffer)
                              throws IkeException {
                          Transform[] transformArray = new Transform[count];
                          for (int i = 0; i < count; i++) {
                              Transform transform = Transform.readFrom(inputBuffer);
                              if (transform.isSupported) {
                                  transformArray[i] = transform;
                              }
                          }
                          return transformArray;
                      }
                  };
  
          public final byte number;
          /** All supported protocol will fall into {@link ProtocolId} */
          public final int protocolId;
  
          public final byte spiSize;
          public final long spi;
  
          public final SaProposal saProposal;
  
          public final boolean hasUnrecognizedTransform;
  
          // TODO: Validate this proposal
  
          @VisibleForTesting
          Proposal(
                  byte number,
                  int protocolId,
                  byte spiSize,
                  long spi,
                  SaProposal saProposal,
                  boolean hasUnrecognizedTransform) {
              this.number = number;
              this.protocolId = protocolId;
              this.spiSize = spiSize;
              this.spi = spi;
              this.saProposal = saProposal;
              this.hasUnrecognizedTransform = hasUnrecognizedTransform;
          }
  
          @VisibleForTesting
          static Proposal readFrom(ByteBuffer inputBuffer) throws IkeException {
              byte isLast = inputBuffer.get();
              if (isLast != LAST_PROPOSAL && isLast != NOT_LAST_PROPOSAL) {
                  throw new InvalidSyntaxException(
                          "Invalid value of Last Proposal Substructure: " + isLast);
              }
              // Skip RESERVED byte
              inputBuffer.get(new byte[PROPOSAL_RESERVED_FIELD_LEN]);
  
              int length = Short.toUnsignedInt(inputBuffer.getShort());
              byte number = inputBuffer.get();
              int protocolId = Byte.toUnsignedInt(inputBuffer.get());
  
              byte spiSize = inputBuffer.get();
              int transformCount = Byte.toUnsignedInt(inputBuffer.get());
  
              // TODO: Add check: spiSize must be 0 in initial IKE SA negotiation
              // spiSize should be either 8 for IKE or 4 for IPsec.
              long spi = SPI_NOT_INCLUDED;
              switch (spiSize) {
                  case SPI_LEN_NOT_INCLUDED:
                      // No SPI attached for IKE initial exchange.
                      break;
                  case SPI_LEN_IPSEC:
                      spi = Integer.toUnsignedLong(inputBuffer.getInt());
                      break;
                  case SPI_LEN_IKE:
                      spi = inputBuffer.getLong();
                      break;
                  default:
                      throw new InvalidSyntaxException(
                              "Invalid value of spiSize in Proposal Substructure: " + spiSize);
              }
  
              Transform[] transformArray =
                      sTransformDecoder.decodeTransforms(transformCount, inputBuffer);
              // TODO: Validate that sum of all Transforms' lengths plus Proposal header length equals
              // to Proposal's length.
  
              List<EncryptionTransform> encryptAlgoList = new LinkedList<>();
              List<PrfTransform> prfList = new LinkedList<>();
              List<IntegrityTransform> integAlgoList = new LinkedList<>();
              List<DhGroupTransform> dhGroupList = new LinkedList<>();
              List<EsnTransform> esnList = new LinkedList<>();
  
              boolean hasUnrecognizedTransform = false;
  
              for (Transform transform : transformArray) {
                  switch (transform.type) {
                      case Transform.TRANSFORM_TYPE_ENCR:
                          encryptAlgoList.add((EncryptionTransform) transform);
                          break;
                      case Transform.TRANSFORM_TYPE_PRF:
                          prfList.add((PrfTransform) transform);
                          break;
                      case Transform.TRANSFORM_TYPE_INTEG:
                          integAlgoList.add((IntegrityTransform) transform);
                          break;
                      case Transform.TRANSFORM_TYPE_DH:
                          dhGroupList.add((DhGroupTransform) transform);
                          break;
                      case Transform.TRANSFORM_TYPE_ESN:
                          esnList.add((EsnTransform) transform);
                          break;
                      default:
                          hasUnrecognizedTransform = true;
                  }
              }
  
              SaProposal saProposal =
                      new SaProposal(
                              protocolId,
                              encryptAlgoList.toArray(
                                      new EncryptionTransform[encryptAlgoList.size()]),
                              prfList.toArray(new PrfTransform[prfList.size()]),
                              integAlgoList.toArray(new IntegrityTransform[integAlgoList.size()]),
                              dhGroupList.toArray(new DhGroupTransform[dhGroupList.size()]),
                              esnList.toArray(new EsnTransform[esnList.size()]));
  
              return new Proposal(
                      number, protocolId, spiSize, spi, saProposal, hasUnrecognizedTransform);
          }
          // TODO: Add another contructor for encoding.
  
          /** Package private */
          boolean isNegotiatedFrom(Proposal reqProposal) {
              if (protocolId != reqProposal.protocolId || number != reqProposal.number) {
                  return false;
              }
              return saProposal.isNegotiatedFrom(reqProposal.saProposal);
          }
  
          protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
              Transform[] allTransforms = saProposal.getAllTransforms();
              byte isLastIndicator = isLast ? LAST_PROPOSAL : NOT_LAST_PROPOSAL;
  
              byteBuffer
                      .put(isLastIndicator)
                      .put(new byte[PROPOSAL_RESERVED_FIELD_LEN])
                      .putShort((short) getProposalLength())
                      .put(number)
                      .put((byte) protocolId)
                      .put(spiSize)
                      .put((byte) allTransforms.length);
  
              switch (spiSize) {
                  case SPI_LEN_NOT_INCLUDED:
                      // No SPI attached for IKE initial exchange.
                      break;
                  case SPI_LEN_IPSEC:
                      byteBuffer.putInt((int) spi);
                      break;
                  case SPI_LEN_IKE:
                      byteBuffer.putLong((long) spi);
                      break;
                  default:
                      throw new IllegalArgumentException(
                              "Invalid value of spiSize in Proposal Substructure: " + spiSize);
              }
  
              // Encode all Transform.
              for (int i = 0; i < allTransforms.length; i++) {
                  // The last transform has the isLast flag set to true.
                  allTransforms[i].encodeToByteBuffer(i == allTransforms.length - 1, byteBuffer);
              }
          }
  
          protected int getProposalLength() {
              int len = PROPOSAL_HEADER_LEN + spiSize;
  
              Transform[] allTransforms = saProposal.getAllTransforms();
              for (Transform t : allTransforms) len += t.getTransformLength();
              return len;
          }
      }
  
      @VisibleForTesting
      interface AttributeDecoder {
          List<Attribute> decodeAttributes(int length, ByteBuffer inputBuffer) throws IkeException;
      }
  
      /**
       * Transform is an abstract base class that represents the common information for all Transform
       * types. It may contain one or more {@link Attribute}.
       *
       * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
       *     Exchange Protocol Version 2 (IKEv2)</a>
       *     <p>Transforms with unrecognized Transform ID or containing unrecognized Attribute Type
       *     shall be ignored when processing received SA payload.
       */
      public abstract static class Transform {
  
          @Retention(RetentionPolicy.SOURCE)
          @IntDef({
              TRANSFORM_TYPE_ENCR,
              TRANSFORM_TYPE_PRF,
              TRANSFORM_TYPE_INTEG,
              TRANSFORM_TYPE_DH,
              TRANSFORM_TYPE_ESN
          })
          public @interface TransformType {}
  
          public static final int TRANSFORM_TYPE_ENCR = 1;
          public static final int TRANSFORM_TYPE_PRF = 2;
          public static final int TRANSFORM_TYPE_INTEG = 3;
          public static final int TRANSFORM_TYPE_DH = 4;
          public static final int TRANSFORM_TYPE_ESN = 5;
  
          private static final byte LAST_TRANSFORM = 0;
          private static final byte NOT_LAST_TRANSFORM = 3;
  
          // Length of reserved field of a Transform.
          private static final int TRANSFORM_RESERVED_FIELD_LEN = 1;
  
          // Length of the Transform that with no Attribute.
          protected static final int BASIC_TRANSFORM_LEN = 8;
  
          // TODO: Add constants for supported algorithms
  
          @VisibleForTesting
          static AttributeDecoder sAttributeDecoder =
                  new AttributeDecoder() {
                      public List<Attribute> decodeAttributes(int length, ByteBuffer inputBuffer)
                              throws IkeException {
                          List<Attribute> list = new LinkedList<>();
                          int parsedLength = BASIC_TRANSFORM_LEN;
                          while (parsedLength < length) {
                              Pair<Attribute, Integer> pair = Attribute.readFrom(inputBuffer);
                              parsedLength += pair.second;
                              list.add(pair.first);
                          }
                          // TODO: Validate that parsedLength equals to length.
                          return list;
                      }
                  };
  
          // Only supported type falls into {@link TransformType}
          public final int type;
          public final int id;
          public final boolean isSupported;
  
          /** Construct an instance of Transform for building an outbound packet. */
          protected Transform(int type, int id) {
              this.type = type;
              this.id = id;
              if (!isSupportedTransformId(id)) {
                  throw new IllegalArgumentException(
                          "Unsupported " + getTransformTypeString() + " Algorithm ID: " + id);
              }
              this.isSupported = true;
          }
  
          /** Construct an instance of Transform for decoding an inbound packet. */
          protected Transform(int type, int id, List<Attribute> attributeList) {
              this.type = type;
              this.id = id;
              this.isSupported =
                      isSupportedTransformId(id) && !hasUnrecognizedAttribute(attributeList);
          }
  
          @VisibleForTesting
          static Transform readFrom(ByteBuffer inputBuffer) throws IkeException {
              byte isLast = inputBuffer.get();
              if (isLast != LAST_TRANSFORM && isLast != NOT_LAST_TRANSFORM) {
                  throw new InvalidSyntaxException(
                          "Invalid value of Last Transform Substructure: " + isLast);
              }
  
              // Skip RESERVED byte
              inputBuffer.get(new byte[TRANSFORM_RESERVED_FIELD_LEN]);
  
              int length = Short.toUnsignedInt(inputBuffer.getShort());
              int type = Byte.toUnsignedInt(inputBuffer.get());
  
              // Skip RESERVED byte
              inputBuffer.get(new byte[TRANSFORM_RESERVED_FIELD_LEN]);
  
              int id = Short.toUnsignedInt(inputBuffer.getShort());
  
              // Decode attributes
              List<Attribute> attributeList = sAttributeDecoder.decodeAttributes(length, inputBuffer);
  
              validateAttributeUniqueness(attributeList);
  
              switch (type) {
                  case TRANSFORM_TYPE_ENCR:
                      return new EncryptionTransform(id, attributeList);
                  case TRANSFORM_TYPE_PRF:
                      return new PrfTransform(id, attributeList);
                  case TRANSFORM_TYPE_INTEG:
                      return new IntegrityTransform(id, attributeList);
                  case TRANSFORM_TYPE_DH:
                      return new DhGroupTransform(id, attributeList);
                  case TRANSFORM_TYPE_ESN:
                      return new EsnTransform(id, attributeList);
                  default:
                      return new UnrecognizedTransform(type, id, attributeList);
              }
          }
  
          // Throw InvalidSyntaxException if there are multiple Attributes of the same type
          private static void validateAttributeUniqueness(List<Attribute> attributeList)
                  throws IkeException {
              Set<Integer> foundTypes = new ArraySet<>();
              for (Attribute attr : attributeList) {
                  if (!foundTypes.add(attr.type)) {
                      throw new InvalidSyntaxException(
                              "There are multiple Attributes of the same type. ");
                  }
              }
          }
  
          // Check if there is Attribute with unrecognized type.
          protected abstract boolean hasUnrecognizedAttribute(List<Attribute> attributeList);
  
          // Check if this Transform ID is supported.
          protected abstract boolean isSupportedTransformId(int id);
  
          // Encode Transform to a ByteBuffer.
          protected abstract void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer);
  
          // Get entire Transform length.
          protected abstract int getTransformLength();
  
          protected void encodeBasicTransformToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
              byte isLastIndicator = isLast ? LAST_TRANSFORM : NOT_LAST_TRANSFORM;
              byteBuffer
                      .put(isLastIndicator)
                      .put(new byte[TRANSFORM_RESERVED_FIELD_LEN])
                      .putShort((short) getTransformLength())
                      .put((byte) type)
                      .put(new byte[TRANSFORM_RESERVED_FIELD_LEN])
                      .putShort((short) id);
          }
  
          /**
           * Get Tranform Type as a String.
           *
           * @return Tranform Type as a String.
           */
          public abstract String getTransformTypeString();
  
          // TODO: Add abstract getTransformIdString() to return specific algorithm/dhGroup name
      }
  
      /**
       * EncryptionTransform represents an encryption algorithm. It may contain an Atrribute
       * specifying the key length.
       *
       * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
       *     Exchange Protocol Version 2 (IKEv2)</a>
       */
      public static final class EncryptionTransform extends Transform {
          private static final int KEY_LEN_UNSPECIFIED = 0;
  
          // When using encryption algorithm with variable-length keys, mSpecifiedKeyLength MUST be
          // set and a KeyLengthAttribute MUST be attached. Otherwise, mSpecifiedKeyLength MUST NOT be
          // set and KeyLengthAttribute MUST NOT be attached.
          private final int mSpecifiedKeyLength;
  
          /**
           * Contruct an instance of EncryptionTransform with fixed key length for building an
           * outbound packet.
           *
           * @param id the IKE standard Transform ID.
           */
          public EncryptionTransform(@EncryptionAlgorithm int id) {
              this(id, KEY_LEN_UNSPECIFIED);
          }
  
          /**
           * Contruct an instance of EncryptionTransform with variable key length for building an
           * outbound packet.
           *
           * @param id the IKE standard Transform ID.
           * @param specifiedKeyLength the specified key length of this encryption algorithm.
           */
          public EncryptionTransform(@EncryptionAlgorithm int id, int specifiedKeyLength) {
              super(Transform.TRANSFORM_TYPE_ENCR, id);
  
              mSpecifiedKeyLength = specifiedKeyLength;
              try {
                  validateKeyLength();
              } catch (InvalidSyntaxException e) {
                  throw new IllegalArgumentException(e);
              }
          }
  
          /**
           * Contruct an instance of EncryptionTransform for decoding an inbound packet.
           *
           * @param id the IKE standard Transform ID.
           * @param attributeList the decoded list of Attribute.
           * @throws InvalidSyntaxException for syntax error.
           */
          protected EncryptionTransform(int id, List<Attribute> attributeList)
                  throws InvalidSyntaxException {
              super(Transform.TRANSFORM_TYPE_ENCR, id, attributeList);
              if (!isSupported) {
                  mSpecifiedKeyLength = KEY_LEN_UNSPECIFIED;
              } else {
                  if (attributeList.size() == 0) {
                      mSpecifiedKeyLength = KEY_LEN_UNSPECIFIED;
                  } else {
                      KeyLengthAttribute attr = getKeyLengthAttribute(attributeList);
                      mSpecifiedKeyLength = attr.keyLength;
                  }
                  validateKeyLength();
              }
          }
  
          @Override
          public int hashCode() {
              return Objects.hash(type, id, mSpecifiedKeyLength);
          }
  
          @Override
          public boolean equals(Object o) {
              if (!(o instanceof EncryptionTransform)) return false;
  
              EncryptionTransform other = (EncryptionTransform) o;
              return (type == other.type
                      && id == other.id
                      && mSpecifiedKeyLength == other.mSpecifiedKeyLength);
          }
  
          @Override
          protected boolean isSupportedTransformId(int id) {
              return SaProposal.isSupportedEncryptionAlgorithm(id);
          }
  
          @Override
          protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
              for (Attribute attr : attributeList) {
                  if (attr instanceof UnrecognizedAttribute) {
                      return true;
                  }
              }
              return false;
          }
  
          private KeyLengthAttribute getKeyLengthAttribute(List<Attribute> attributeList) {
              for (Attribute attr : attributeList) {
                  if (attr.type == Attribute.ATTRIBUTE_TYPE_KEY_LENGTH) {
                      return (KeyLengthAttribute) attr;
                  }
              }
              throw new IllegalArgumentException("Cannot find Attribute with Key Length type");
          }
  
          private void validateKeyLength() throws InvalidSyntaxException {
              switch (id) {
                  case SaProposal.ENCRYPTION_ALGORITHM_3DES:
                      if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
                          throw new InvalidSyntaxException(
                                  "Must not set Key Length value for this "
                                          + getTransformTypeString()
                                          + " Algorithm ID: "
                                          + id);
                      }
                      return;
                  case SaProposal.ENCRYPTION_ALGORITHM_AES_CBC:
                      /* fall through */
                  case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8:
                      /* fall through */
                  case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12:
                      /* fall through */
                  case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16:
                      if (mSpecifiedKeyLength == KEY_LEN_UNSPECIFIED) {
                          throw new InvalidSyntaxException(
                                  "Must set Key Length value for this "
                                          + getTransformTypeString()
                                          + " Algorithm ID: "
                                          + id);
                      }
                      if (mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_128
                              && mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_192
                              && mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_256) {
                          throw new InvalidSyntaxException(
                                  "Invalid key length for this "
                                          + getTransformTypeString()
                                          + " Algorithm ID: "
                                          + id);
                      }
                      return;
                  default:
                      // Won't hit here.
                      throw new IllegalArgumentException(
                              "Unrecognized Encryption Algorithm ID: " + id);
              }
          }
  
          @Override
          protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
              encodeBasicTransformToByteBuffer(isLast, byteBuffer);
  
              if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
                  new KeyLengthAttribute(mSpecifiedKeyLength).encodeToByteBuffer(byteBuffer);
              }
          }
  
          @Override
          protected int getTransformLength() {
              int len = BASIC_TRANSFORM_LEN;
  
              if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
                  len += new KeyLengthAttribute(mSpecifiedKeyLength).getAttributeLength();
              }
  
              return len;
          }
  
          @Override
          public String getTransformTypeString() {
              return "Encryption Algorithm";
          }
      }
  
      /**
       * PrfTransform represents an pseudorandom function.
       *
       * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
       *     Exchange Protocol Version 2 (IKEv2)</a>
       */
      public static final class PrfTransform extends Transform {
          /**
           * Contruct an instance of PrfTransform for building an outbound packet.
           *
           * @param id the IKE standard Transform ID.
           */
          public PrfTransform(@PseudorandomFunction int id) {
              super(Transform.TRANSFORM_TYPE_PRF, id);
          }
  
          /**
           * Contruct an instance of PrfTransform for decoding an inbound packet.
           *
           * @param id the IKE standard Transform ID.
           * @param attributeList the decoded list of Attribute.
           * @throws InvalidSyntaxException for syntax error.
           */
          protected PrfTransform(int id, List<Attribute> attributeList)
                  throws InvalidSyntaxException {
              super(Transform.TRANSFORM_TYPE_PRF, id, attributeList);
          }
  
          @Override
          public int hashCode() {
              return Objects.hash(type, id);
          }
  
          @Override
          public boolean equals(Object o) {
              if (!(o instanceof PrfTransform)) return false;
  
              PrfTransform other = (PrfTransform) o;
              return (type == other.type && id == other.id);
          }
  
          @Override
          protected boolean isSupportedTransformId(int id) {
              return SaProposal.isSupportedPseudorandomFunction(id);
          }
  
          @Override
          protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
              return !attributeList.isEmpty();
          }
  
          @Override
          protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
              encodeBasicTransformToByteBuffer(isLast, byteBuffer);
          }
  
          @Override
          protected int getTransformLength() {
              return BASIC_TRANSFORM_LEN;
          }
  
          @Override
          public String getTransformTypeString() {
              return "Pseudorandom Function";
          }
      }
  
      /**
       * IntegrityTransform represents an integrity algorithm.
       *
       * <p>Proposing integrity algorithm for ESP SA is optional. Omitting the IntegrityTransform is
       * equivalent to including it with a value of NONE. When multiple integrity algorithms are
       * provided, choosing any of them are acceptable.
       *
       * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
       *     Exchange Protocol Version 2 (IKEv2)</a>
       */
      public static final class IntegrityTransform extends Transform {
          /**
           * Contruct an instance of IntegrityTransform for building an outbound packet.
           *
           * @param id the IKE standard Transform ID.
           */
          public IntegrityTransform(@IntegrityAlgorithm int id) {
              super(Transform.TRANSFORM_TYPE_INTEG, id);
          }
  
          /**
           * Contruct an instance of IntegrityTransform for decoding an inbound packet.
           *
           * @param id the IKE standard Transform ID.
           * @param attributeList the decoded list of Attribute.
           * @throws InvalidSyntaxException for syntax error.
           */
          protected IntegrityTransform(int id, List<Attribute> attributeList)
                  throws InvalidSyntaxException {
              super(Transform.TRANSFORM_TYPE_INTEG, id, attributeList);
          }
  
          @Override
          public int hashCode() {
              return Objects.hash(type, id);
          }
  
          @Override
          public boolean equals(Object o) {
              if (!(o instanceof IntegrityTransform)) return false;
  
              IntegrityTransform other = (IntegrityTransform) o;
              return (type == other.type && id == other.id);
          }
  
          @Override
          protected boolean isSupportedTransformId(int id) {
              return SaProposal.isSupportedIntegrityAlgorithm(id);
          }
  
          @Override
          protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
              return !attributeList.isEmpty();
          }
  
          @Override
          protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
              encodeBasicTransformToByteBuffer(isLast, byteBuffer);
          }
  
          @Override
          protected int getTransformLength() {
              return BASIC_TRANSFORM_LEN;
          }
  
          @Override
          public String getTransformTypeString() {
              return "Integrity Algorithm";
          }
      }
  
      /**
       * DhGroupTransform represents a Diffie-Hellman Group
       *
       * <p>Proposing DH group for non-first Child SA is optional. Omitting the DhGroupTransform is
       * equivalent to including it with a value of NONE. When multiple DH groups are provided,
       * choosing any of them are acceptable.
       *
       * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
       *     Exchange Protocol Version 2 (IKEv2)</a>
       */
      public static final class DhGroupTransform extends Transform {
          /**
           * Contruct an instance of DhGroupTransform for building an outbound packet.
           *
           * @param id the IKE standard Transform ID.
           */
          public DhGroupTransform(@DhGroup int id) {
              super(Transform.TRANSFORM_TYPE_DH, id);
          }
  
          /**
           * Contruct an instance of DhGroupTransform for decoding an inbound packet.
           *
           * @param id the IKE standard Transform ID.
           * @param attributeList the decoded list of Attribute.
           * @throws InvalidSyntaxException for syntax error.
           */
          protected DhGroupTransform(int id, List<Attribute> attributeList)
                  throws InvalidSyntaxException {
              super(Transform.TRANSFORM_TYPE_DH, id, attributeList);
          }
  
          @Override
          public int hashCode() {
              return Objects.hash(type, id);
          }
  
          @Override
          public boolean equals(Object o) {
              if (!(o instanceof DhGroupTransform)) return false;
  
              DhGroupTransform other = (DhGroupTransform) o;
              return (type == other.type && id == other.id);
          }
  
          @Override
          protected boolean isSupportedTransformId(int id) {
              return SaProposal.isSupportedDhGroup(id);
          }
  
          @Override
          protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
              return !attributeList.isEmpty();
          }
  
          @Override
          protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
              encodeBasicTransformToByteBuffer(isLast, byteBuffer);
          }
  
          @Override
          protected int getTransformLength() {
              return BASIC_TRANSFORM_LEN;
          }
  
          @Override
          public String getTransformTypeString() {
              return "Diffie-Hellman Group";
          }
      }
  
      /**
       * EsnTransform represents ESN policy that indicates if IPsec SA uses tranditional 32-bit
       * sequence numbers or extended(64-bit) sequence numbers.
       *
       * <p>Currently IKE library only supports negotiating IPsec SA that do not use extended sequence
       * numbers. The Transform ID of EsnTransform in outbound packets is not user configurable.
       *
       * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296, Internet Key
       *     Exchange Protocol Version 2 (IKEv2)</a>
       */
      public static final class EsnTransform extends Transform {
          @Retention(RetentionPolicy.SOURCE)
          @IntDef({ESN_POLICY_NO_EXTENDED, ESN_POLICY_EXTENDED})
          public @interface EsnPolicy {}
  
          public static final int ESN_POLICY_NO_EXTENDED = 0;
          public static final int ESN_POLICY_EXTENDED = 1;
  
          /**
           * Construct an instance of EsnTransform indicates using no-extended sequence numbers for
           * building an outbound packet.
           */
          public EsnTransform() {
              super(Transform.TRANSFORM_TYPE_ESN, ESN_POLICY_NO_EXTENDED);
          }
  
          /**
           * Contruct an instance of EsnTransform for decoding an inbound packet.
           *
           * @param id the IKE standard Transform ID.
           * @param attributeList the decoded list of Attribute.
           * @throws InvalidSyntaxException for syntax error.
           */
          protected EsnTransform(int id, List<Attribute> attributeList)
                  throws InvalidSyntaxException {
              super(Transform.TRANSFORM_TYPE_ESN, id, attributeList);
          }
  
          @Override
          public int hashCode() {
              return Objects.hash(type, id);
          }
  
          @Override
          public boolean equals(Object o) {
              if (!(o instanceof EsnTransform)) return false;
  
              EsnTransform other = (EsnTransform) o;
              return (type == other.type && id == other.id);
          }
  
          @Override
          protected boolean isSupportedTransformId(int id) {
              return (id == ESN_POLICY_NO_EXTENDED || id == ESN_POLICY_EXTENDED);
          }
  
          @Override
          protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
              return !attributeList.isEmpty();
          }
  
          @Override
          protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
              encodeBasicTransformToByteBuffer(isLast, byteBuffer);
          }
  
          @Override
          protected int getTransformLength() {
              return BASIC_TRANSFORM_LEN;
          }
  
          @Override
          public String getTransformTypeString() {
              return "Extended Sequence Numbers";
          }
      }
  
      /**
       * UnrecognizedTransform represents a Transform with unrecognized Transform Type.
       *
       * <p>Proposals containing an UnrecognizedTransform should be ignored.
       */
      protected static final class UnrecognizedTransform extends Transform {
          protected UnrecognizedTransform(int type, int id, List<Attribute> attributeList) {
              super(type, id, attributeList);
          }
  
          @Override
          protected boolean isSupportedTransformId(int id) {
              return false;
          }
  
          @Override
          protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
              return !attributeList.isEmpty();
          }
  
          @Override
          protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
              throw new UnsupportedOperationException(
                      "It is not supported to encode a Transform with" + getTransformTypeString());
          }
  
          @Override
          protected int getTransformLength() {
              throw new UnsupportedOperationException(
                      "It is not supported to get length of a Transform with "
                              + getTransformTypeString());
          }
  
          /**
           * Return Tranform Type of Unrecognized Transform as a String.
           *
           * @return Tranform Type of Unrecognized Transform as a String.
           */
          @Override
          public String getTransformTypeString() {
              return "Unrecognized Transform Type.";
          }
      }
  
      /**
       * Attribute is an abtract base class for completing the specification of some {@link
       * Transform}.
       *
       * <p>Attribute is either in Type/Value format or Type/Length/Value format. For TV format,
       * Attribute length is always 4 bytes containing value for 2 bytes. While for TLV format,
       * Attribute length is determined by length field.
       *
       * <p>Currently only Key Length type is supported
       *
       * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.5">RFC 7296, Internet Key
       *     Exchange Protocol Version 2 (IKEv2)</a>
       */
      public abstract static class Attribute {
          @Retention(RetentionPolicy.SOURCE)
          @IntDef({ATTRIBUTE_TYPE_KEY_LENGTH})
          public @interface AttributeType {}
  
          // Support only one Attribute type: Key Length. Should use Type/Value format.
          public static final int ATTRIBUTE_TYPE_KEY_LENGTH = 14;
  
          // Mask to extract the left most AF bit to indicate Attribute Format.
          private static final int ATTRIBUTE_FORMAT_MASK = 0x8000;
          // Mask to extract 15 bits after the AF bit to indicate Attribute Type.
          private static final int ATTRIBUTE_TYPE_MASK = 0x7fff;
  
          // Package private mask to indicate that Type-Value (TV) Attribute Format is used.
          static final int ATTRIBUTE_FORMAT_TV = ATTRIBUTE_FORMAT_MASK;
  
          // Package private
          static final int TV_ATTRIBUTE_VALUE_LEN = 2;
          static final int TV_ATTRIBUTE_TOTAL_LEN = 4;
          static final int TVL_ATTRIBUTE_HEADER_LEN = TV_ATTRIBUTE_TOTAL_LEN;
  
          // Only Key Length type belongs to AttributeType
          public final int type;
  
          /** Construct an instance of an Attribute when decoding message. */
          protected Attribute(int type) {
              this.type = type;
          }
  
          @VisibleForTesting
          static Pair<Attribute, Integer> readFrom(ByteBuffer inputBuffer) throws IkeException {
              short formatAndType = inputBuffer.getShort();
              int format = formatAndType & ATTRIBUTE_FORMAT_MASK;
              int type = formatAndType & ATTRIBUTE_TYPE_MASK;
  
              int length = 0;
              byte[] value = new byte[0];
              if (format == ATTRIBUTE_FORMAT_TV) {
                  // Type/Value format
                  length = TV_ATTRIBUTE_TOTAL_LEN;
                  value = new byte[TV_ATTRIBUTE_VALUE_LEN];
              } else {
                  // Type/Length/Value format
                  if (type == ATTRIBUTE_TYPE_KEY_LENGTH) {
                      throw new InvalidSyntaxException("Wrong format in Transform Attribute");
                  }
  
                  length = Short.toUnsignedInt(inputBuffer.getShort());
                  int valueLen = length - TVL_ATTRIBUTE_HEADER_LEN;
                  // IkeMessage will catch exception if valueLen is negative.
                  value = new byte[valueLen];
              }
  
              inputBuffer.get(value);
  
              switch (type) {
                  case ATTRIBUTE_TYPE_KEY_LENGTH:
                      return new Pair(new KeyLengthAttribute(value), length);
                  default:
                      return new Pair(new UnrecognizedAttribute(type, value), length);
              }
          }
  
          // Encode Attribute to a ByteBuffer.
          protected abstract void encodeToByteBuffer(ByteBuffer byteBuffer);
  
          // Get entire Attribute length.
          protected abstract int getAttributeLength();
      }
  
      /** KeyLengthAttribute represents a Key Length type Attribute */
      public static final class KeyLengthAttribute extends Attribute {
          public final int keyLength;
  
          protected KeyLengthAttribute(byte[] value) {
              this(Short.toUnsignedInt(ByteBuffer.wrap(value).getShort()));
          }
  
          protected KeyLengthAttribute(int keyLength) {
              super(ATTRIBUTE_TYPE_KEY_LENGTH);
              this.keyLength = keyLength;
          }
  
          @Override
          protected void encodeToByteBuffer(ByteBuffer byteBuffer) {
              byteBuffer
                      .putShort((short) (ATTRIBUTE_FORMAT_TV | ATTRIBUTE_TYPE_KEY_LENGTH))
                      .putShort((short) keyLength);
          }
  
          @Override
          protected int getAttributeLength() {
              return TV_ATTRIBUTE_TOTAL_LEN;
          }
      }
  
      /**
       * UnrecognizedAttribute represents a Attribute with unrecoginzed Attribute Type.
       *
       * <p>Transforms containing UnrecognizedAttribute should be ignored.
       */
      protected static final class UnrecognizedAttribute extends Attribute {
          protected UnrecognizedAttribute(int type, byte[] value) {
              super(type);
          }
  
          @Override
          protected void encodeToByteBuffer(ByteBuffer byteBuffer) {
              throw new UnsupportedOperationException(
                      "It is not supported to encode an unrecognized Attribute.");
          }
  
          @Override
          protected int getAttributeLength() {
              throw new UnsupportedOperationException(
                      "It is not supported to get length of an unrecognized Attribute.");
          }
      }
  
      /**
       * Encode SA payload to ByteBUffer.
       *
       * @param nextPayload type of payload that follows this payload.
       * @param byteBuffer destination ByteBuffer that stores encoded payload.
       */
      @Override
      protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
          encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer);
  
          for (int i = 0; i < proposalList.size(); i++) {
              // The last proposal has the isLast flag set to true.
              proposalList.get(i).encodeToByteBuffer(i == proposalList.size() - 1, byteBuffer);
          }
      }
  
      /**
       * Get entire payload length.
       *
       * @return entire payload length.
       */
      @Override
      protected int getPayloadLength() {
          int len = GENERIC_HEADER_LENGTH;
  
          for (Proposal p : proposalList) len += p.getProposalLength();
  
          return len;
      }
  
      /**
       * Return the payload type as a String.
       *
       * @return the payload type as a String.
       */
      @Override
      public String getTypeString() {
          return "SA Payload";
      }
  }
  