Support decoding Traffic Selector am: 68fde6e39c
am: e2304e57d3

Change-Id: Ie3f0edc4c5c9e01f49c82822cb2aad13d10bb2ab
diff --git a/src/java/com/android/ike/ikev2/IkeTrafficSelector.java b/src/java/com/android/ike/ikev2/IkeTrafficSelector.java
new file mode 100644
index 0000000..baebe64
--- /dev/null
+++ b/src/java/com/android/ike/ikev2/IkeTrafficSelector.java
@@ -0,0 +1,231 @@
+/*
+ * 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;
+    }
+}
diff --git a/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java b/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java
index ea4b55a..489eeff 100644
--- a/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java
+++ b/src/java/com/android/ike/ikev2/exceptions/InvalidSyntaxException.java
@@ -35,4 +35,13 @@
     public InvalidSyntaxException(String message) {
         super(IkeNotifyPayload.NOTIFY_TYPE_INVALID_SYNTAX);
     }
+
+    /**
+     * Construct a instance of InvalidSyntaxException.
+     *
+     * @param cause the reason of exception.
+     */
+    public InvalidSyntaxException(Throwable cause) {
+        super(IkeNotifyPayload.NOTIFY_TYPE_INVALID_SYNTAX, cause);
+    }
 }
diff --git a/src/java/com/android/ike/ikev2/message/IkeTsPayload.java b/src/java/com/android/ike/ikev2/message/IkeTsPayload.java
index 9763af2..8231e29 100644
--- a/src/java/com/android/ike/ikev2/message/IkeTsPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeTsPayload.java
@@ -16,6 +16,7 @@
 
 package com.android.ike.ikev2.message;
 
+import com.android.ike.ikev2.IkeTrafficSelector;
 import com.android.ike.ikev2.exceptions.IkeException;
 
 import java.nio.ByteBuffer;
@@ -37,6 +38,7 @@
 
     /** Number of Traffic Selectors */
     public final int numTs;
+    public final IkeTrafficSelector[] trafficSelectors;
 
     IkeTsPayload(boolean critical, byte[] payloadBody, boolean isInitiator) throws IkeException {
         super((isInitiator ? PAYLOAD_TYPE_TS_INITIATOR : PAYLOAD_TYPE_TS_RESPONDER), critical);
@@ -46,7 +48,10 @@
         // Skip RESERVED byte
         inputBuffer.get(new byte[TS_HEADER_RESERVED_LEN]);
 
-        // TODO: Decode Traffic Selectors.
+        // Decode Traffic Selectors
+        byte[] tsBytes = new byte[inputBuffer.remaining()];
+        inputBuffer.get(tsBytes);
+        trafficSelectors = IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
     }
 
     /**
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/IkeTrafficSelectorTest.java b/tests/iketests/src/java/com/android/ike/ikev2/IkeTrafficSelectorTest.java
new file mode 100644
index 0000000..1982e62
--- /dev/null
+++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeTrafficSelectorTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
+import com.android.ike.ikev2.message.TestUtils;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.Test;
+
+import java.net.Inet4Address;
+
+public final class IkeTrafficSelectorTest {
+    private static final String TS_IPV4_ONE_HEX_STRING = "070000100010fff0c0000264c0000365";
+    private static final int TS_ONE_START_PORT = 16;
+    private static final int TS_ONE_END_PORT = 65520;
+    private static final Inet4Address TS_ONE_START_ADDRESS =
+            (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100"));
+    private static final Inet4Address TS_ONE_END_ADDRESS =
+            (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.3.101"));
+
+    private static final String TS_IPV4_TWO_HEX_STRING = "070000100000ffffc0000464c0000466";
+    private static final int TS_TWO_START_PORT = 0;
+    private static final int TS_TWO_END_PORT = 65535;
+    private static final Inet4Address TS_TWO_START_ADDRESS =
+            (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.4.100"));
+    private static final Inet4Address TS_TWO_END_ADDRESS =
+            (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.4.102"));
+
+    private static final String TX_IPV4_INVALID_PORT_RANGE_HEX_STRING =
+            "0700001022221111c0000464c0000466";
+    private static final String TX_IPV4_INVALID_ADDRESS_RANGE_HEX_STRING =
+            "070000100000ffffc0000466c0000366";
+
+    private static final int TS_TYPE_OFFSET = 0;
+    private static final int PROTOCOL_ID_OFFSET = 1;
+    private static final int TS_LENGTH_OFFSET = 2;
+
+    @Test
+    public void testDecodeIkeTrafficSelectors() throws Exception {
+        int numTs = 2;
+
+        byte[] tsBytes =
+                TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING + TS_IPV4_TWO_HEX_STRING);
+        IkeTrafficSelector[] selectors =
+                IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+
+        assertEquals(numTs, selectors.length);
+
+        // Verify first traffic selector
+        IkeTrafficSelector tsOne = selectors[0];
+
+        assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, tsOne.tsType);
+        assertEquals(IkeTrafficSelector.IP_PROTOCOL_ID_UNSPEC, tsOne.ipProtocolId);
+        assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_IPV4_LEN, tsOne.selectorLength);
+        assertEquals(TS_ONE_START_PORT, tsOne.startPort);
+        assertEquals(TS_ONE_END_PORT, tsOne.endPort);
+        assertEquals(TS_ONE_START_ADDRESS, tsOne.startingAddress);
+        assertEquals(TS_ONE_END_ADDRESS, tsOne.endingAddress);
+
+        // Verify second traffic selector
+        IkeTrafficSelector tsTwo = selectors[1];
+
+        assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE, tsTwo.tsType);
+        assertEquals(IkeTrafficSelector.IP_PROTOCOL_ID_UNSPEC, tsTwo.ipProtocolId);
+        assertEquals(IkeTrafficSelector.TRAFFIC_SELECTOR_IPV4_LEN, tsTwo.selectorLength);
+        assertEquals(TS_TWO_START_PORT, tsTwo.startPort);
+        assertEquals(TS_TWO_END_PORT, tsTwo.endPort);
+        assertEquals(TS_TWO_START_ADDRESS, tsTwo.startingAddress);
+        assertEquals(TS_TWO_END_ADDRESS, tsTwo.endingAddress);
+    }
+
+    @Test
+    public void testDecodeIkeTrafficSelectorWithInvalidTsType() throws Exception {
+        int numTs = 1;
+        byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING);
+        tsBytes[TS_TYPE_OFFSET] = -1;
+
+        try {
+            IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+            fail("Expected to fail due to invalid Traffic Selector Type.");
+        } catch (InvalidSyntaxException expected) {
+
+        }
+    }
+
+    @Test
+    public void testDecodeIkeTrafficSelectorWithInvalidIpProtocol() throws Exception {
+        int numTs = 1;
+        byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING);
+        tsBytes[PROTOCOL_ID_OFFSET] = -1;
+
+        try {
+            IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+            fail("Expected to fail due to invalid IP Protocol ID.");
+        } catch (InvalidSyntaxException expected) {
+
+        }
+    }
+
+    @Test
+    public void testDecodeIkeTrafficSelectorWithExpectedTrailing() throws Exception {
+        int numTs = 1;
+        byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING + "FFFF");
+
+        try {
+            IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+            fail("Expected to fail due to unexpected trailing characters.");
+        } catch (InvalidSyntaxException expected) {
+
+        }
+    }
+
+    @Test
+    public void testDecodeIkeTrafficSelectorWithInvalidTsLength() throws Exception {
+        int numTs = 1;
+        byte[] tsBytes = TestUtils.hexStringToByteArray(TS_IPV4_ONE_HEX_STRING);
+
+        // Traffic Selector field is two octets
+        tsBytes[TS_LENGTH_OFFSET] = 0;
+        tsBytes[TS_LENGTH_OFFSET + 1] = 0;
+
+        try {
+            IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+            fail("Expected to fail due to invalid Traffic Selector length.");
+        } catch (InvalidSyntaxException expected) {
+
+        }
+    }
+
+    @Test
+    public void testDecodeIkeTrafficSelectorWithInvalidPortRange() throws Exception {
+        int numTs = 1;
+        byte[] tsBytes = TestUtils.hexStringToByteArray(TX_IPV4_INVALID_PORT_RANGE_HEX_STRING);
+
+        try {
+            IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+            fail("Expected to fail when start port is larger than end port.");
+        } catch (InvalidSyntaxException expected) {
+
+        }
+    }
+
+    @Test
+    public void testDecodeIkeTrafficSelectorWithInvalidAddressRange() throws Exception {
+        int numTs = 1;
+        byte[] tsBytes = TestUtils.hexStringToByteArray(TX_IPV4_INVALID_ADDRESS_RANGE_HEX_STRING);
+
+        try {
+            IkeTrafficSelector.decodeIkeTrafficSelectors(numTs, tsBytes);
+            fail("Expected to fail when starting address is larger than ending address.");
+        } catch (InvalidSyntaxException expected) {
+
+        }
+    }
+}
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java
index 67f394f..f547526 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTsPayloadTest.java
@@ -25,8 +25,8 @@
 
 public final class IkeTsPayloadTest {
     private static final String TS_INITIATOR_PAYLOAD_HEX_STRING =
-            "2d00001801000000070000100000ffff00000000ffffffff";
-    private static final int NUMBER_OF_TS = 1;
+            "2d00002802000000070000100000ffff00000000ffffffff070000100000ffff00000001fffffffe";
+    private static final int NUMBER_OF_TS = 2;
 
     @Test
     public void testDecodeTsInitiatorPayload() throws Exception {