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.annotation.IntDef;
  import android.util.ArraySet;
  
  import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
  import com.android.internal.annotations.VisibleForTesting;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.net.Inet4Address;
  import java.net.InetAddress;
  import java.net.UnknownHostException;
  import java.nio.BufferOverflowException;
  import java.nio.ByteBuffer;
  
  /**
   * IkeTrafficSelector represents a Traffic Selector of a Child SA.
   *
   * <p>IkeTrafficSelector can be constructed by users for initiating Create Child exchange or be
   * constructed from a decoded inbound Traffic Selector Payload.
   *
   * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.13">RFC 7296, Internet Key Exchange
   *     Protocol Version 2 (IKEv2)</a>
   */
  public final class IkeTrafficSelector {
  
      // IpProtocolId consists of standard IP Protocol IDs.
      @Retention(RetentionPolicy.SOURCE)
      @IntDef({IP_PROTOCOL_ID_UNSPEC, IP_PROTOCOL_ID_ICMP, IP_PROTOCOL_ID_TCP, IP_PROTOCOL_ID_UCP})
      public @interface IpProtocolId {}
  
      // Zero value is re-defined by IKE to indicate that all IP protocols are acceptable.
      @VisibleForTesting static final int IP_PROTOCOL_ID_UNSPEC = 0;
      @VisibleForTesting static final int IP_PROTOCOL_ID_ICMP = 1;
      @VisibleForTesting static final int IP_PROTOCOL_ID_TCP = 6;
      @VisibleForTesting static final int IP_PROTOCOL_ID_UCP = 17;
  
      private static final ArraySet<Integer> IP_PROTOCOL_ID_SET = new ArraySet<>();
  
      static {
          IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_UNSPEC);
          IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_ICMP);
          IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_TCP);
          IP_PROTOCOL_ID_SET.add(IP_PROTOCOL_ID_UCP);
      }
  
      /**
       * TrafficSelectorType consists of IKE standard Traffic Selector Types.
       *
       * @see <a
       *     href="https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml">Internet
       *     Key Exchange Version 2 (IKEv2) Parameters</a>
       */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef({TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE})
      public @interface TrafficSelectorType {}
  
      public static final int TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE = 7;
      public static final int TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE = 8;
  
      // TODO: Consider defining these constants in a central place in Connectivity.
      private static final int IPV4_ADDR_LEN = 4;
      private static final int IPV6_ADDR_LEN = 16;
  
      @VisibleForTesting static final int TRAFFIC_SELECTOR_IPV4_LEN = 16;
      @VisibleForTesting static final int TRAFFIC_SELECTOR_IPV6_LEN = 40;
  
      public final int tsType;
      public final int ipProtocolId;
      public final int selectorLength;
      public final int startPort;
      public final int endPort;
      public final InetAddress startingAddress;
      public final InetAddress endingAddress;
  
      private IkeTrafficSelector(
              int tsType,
              int ipProtocolId,
              int selectorLength,
              int startPort,
              int endPort,
              InetAddress startingAddress,
              InetAddress endingAddress) {
          this.tsType = tsType;
          this.ipProtocolId = ipProtocolId;
          this.selectorLength = selectorLength;
          this.startPort = startPort;
          this.endPort = endPort;
          this.startingAddress = startingAddress;
          this.endingAddress = endingAddress;
      }
  
      // TODO: Add a constructor for users to construct IkeTrafficSelector.
  
      /**
       * Decode IkeTrafficSelectors from inbound Traffic Selector Payload.
       *
       * <p>This method is only called by IkeTsPayload when decoding inbound IKE message.
       *
       * @param numTs number or Traffic Selectors
       * @param tsBytes encoded byte array of Traffic Selectors
       * @return an array of decoded IkeTrafficSelectors
       * @throws InvalidSyntaxException if received bytes are malformed.
       */
      public static IkeTrafficSelector[] decodeIkeTrafficSelectors(int numTs, byte[] tsBytes)
              throws InvalidSyntaxException {
          IkeTrafficSelector[] tsArray = new IkeTrafficSelector[numTs];
          ByteBuffer inputBuffer = ByteBuffer.wrap(tsBytes);
  
          try {
              for (int i = 0; i < numTs; i++) {
                  int tsType = Byte.toUnsignedInt(inputBuffer.get());
                  switch (tsType) {
                      case TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE:
                          tsArray[i] = decodeIpv4TrafficSelector(inputBuffer);
                          break;
                      case TRAFFIC_SELECTOR_TYPE_IPV6_ADDR_RANGE:
                          // TODO: Support it.
                          throw new UnsupportedOperationException("Cannot decode this type.");
                      default:
                          throw new InvalidSyntaxException(
                                  "Invalid Traffic Selector type: " + tsType);
                  }
              }
          } catch (BufferOverflowException e) {
              // Throw exception if any Traffic Selector has invalid length.
              throw new InvalidSyntaxException(e);
          }
  
          if (inputBuffer.remaining() != 0) {
              throw new InvalidSyntaxException(
                      "Unexpected trailing characters of Traffic Selectors.");
          }
  
          return tsArray;
      }
  
      // Decode Traffic Selector using IPv4 address range from a ByteBuffer. A BufferOverflowException
      // will be thrown and caught by method caller if operation reaches the input ByteBuffer's limit.
      private static IkeTrafficSelector decodeIpv4TrafficSelector(ByteBuffer inputBuffer)
              throws InvalidSyntaxException {
          // Decode and validate IP Protocol ID
          int ipProtocolId = Byte.toUnsignedInt(inputBuffer.get());
          if (!IP_PROTOCOL_ID_SET.contains(ipProtocolId)) {
              throw new InvalidSyntaxException("Invalid IP Protocol ID.");
          }
  
          // Decode and validate Selector Length
          int tsLength = Short.toUnsignedInt(inputBuffer.getShort());
          if (TRAFFIC_SELECTOR_IPV4_LEN != tsLength) {
              throw new InvalidSyntaxException("Invalid Traffic Selector Length.");
          }
  
          // Decode and validate ports
          int startPort = Short.toUnsignedInt(inputBuffer.getShort());
          int endPort = Short.toUnsignedInt(inputBuffer.getShort());
          if (startPort > endPort) {
              throw new InvalidSyntaxException("Received invalid port range.");
          }
  
          // Decode and validate IPv4 addresses
          byte[] startAddressBytes = new byte[IPV4_ADDR_LEN];
          byte[] endAddressBytes = new byte[IPV4_ADDR_LEN];
          inputBuffer.get(startAddressBytes);
          inputBuffer.get(endAddressBytes);
          try {
              Inet4Address startAddress =
                      (Inet4Address) (Inet4Address.getByAddress(startAddressBytes));
              Inet4Address endAddress = (Inet4Address) (Inet4Address.getByAddress(endAddressBytes));
  
              // Validate address range.
              if (!isInetAddressRangeValid(startAddress, endAddress)) {
                  throw new InvalidSyntaxException("Received invalid IPv4 address range.");
              }
  
              return new IkeTrafficSelector(
                      TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE,
                      ipProtocolId,
                      TRAFFIC_SELECTOR_IPV4_LEN,
                      startPort,
                      endPort,
                      startAddress,
                      endAddress);
          } catch (ClassCastException | UnknownHostException | IllegalArgumentException e) {
              throw new InvalidSyntaxException(e);
          }
      }
  
      // TODO: Add a method for decoding IPv6 traffic selector.
  
      // Validate address range. Caller must ensure two address are same types.
      // TODO: Consider moving it to the platform code in the future.
      private static boolean isInetAddressRangeValid(
              InetAddress startAddress, InetAddress endAddress) {
          byte[] startAddrBytes = startAddress.getAddress();
          byte[] endAddrBytes = endAddress.getAddress();
  
          if (startAddrBytes.length != endAddrBytes.length) {
              throw new IllegalArgumentException("Two addresses are different types.");
          }
  
          for (int i = 0; i < startAddrBytes.length; i++) {
              int unsignedByteStart = Byte.toUnsignedInt(startAddrBytes[i]);
              int unsignedByteEnd = Byte.toUnsignedInt(endAddrBytes[i]);
  
              if (unsignedByteStart < unsignedByteEnd) {
                  return true;
              } else if (unsignedByteStart > unsignedByteEnd) {
                  return false;
              }
          }
          return true;
      }
  }
  