Merge "Encode unencrypted IKE message to byte array"
diff --git a/src/java/com/android/ike/ikev2/SaProposal.java b/src/java/com/android/ike/ikev2/SaProposal.java
new file mode 100644
index 0000000..37a8100
--- /dev/null
+++ b/src/java/com/android/ike/ikev2/SaProposal.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * SaProposal represents a user configured set contains cryptograhic algorithms and key generating
+ * materials for negotiating an IKE or Child SA.
+ *
+ * <p>User must provide at least a valid SaProposal when they are creating a new IKE SA or Child SA.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key Exchange
+ *     Protocol Version 2 (IKEv2).
+ */
+public final class SaProposal {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        ENCRYPTION_ALGORITHM_3DES,
+        ENCRYPTION_ALGORITHM_AES_CBC,
+        ENCRYPTION_ALGORITHM_AES_GCM_8,
+        ENCRYPTION_ALGORITHM_AES_GCM_12,
+        ENCRYPTION_ALGORITHM_AES_GCM_16
+    })
+    public @interface EncryptionAlgorithm {}
+
+    public static final int ENCRYPTION_ALGORITHM_3DES = 3;
+    public static final int ENCRYPTION_ALGORITHM_AES_CBC = 12;
+    public static final int ENCRYPTION_ALGORITHM_AES_GCM_8 = 18;
+    public static final int ENCRYPTION_ALGORITHM_AES_GCM_12 = 19;
+    public static final int ENCRYPTION_ALGORITHM_AES_GCM_16 = 20;
+
+    private static final Set<Integer> SUPPORTED_ENCRYPTION_ALGORITHM;
+
+    static {
+        SUPPORTED_ENCRYPTION_ALGORITHM = new ArraySet<>();
+        SUPPORTED_ENCRYPTION_ALGORITHM.add(SaProposal.ENCRYPTION_ALGORITHM_3DES);
+        SUPPORTED_ENCRYPTION_ALGORITHM.add(SaProposal.ENCRYPTION_ALGORITHM_AES_CBC);
+        SUPPORTED_ENCRYPTION_ALGORITHM.add(SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8);
+        SUPPORTED_ENCRYPTION_ALGORITHM.add(SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12);
+        SUPPORTED_ENCRYPTION_ALGORITHM.add(SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16);
+    }
+
+    public static final int KEY_LEN_AES_128 = 128;
+    public static final int KEY_LEN_AES_192 = 192;
+    public static final int KEY_LEN_AES_256 = 256;
+
+    /**
+     * Check if the provided algorithm is a supported encryption algorithm.
+     *
+     * @param algorithm IKE standard encryption algorithm id
+     * @return if the provided algorithm is a supported encryption algorithm.
+     */
+    public static boolean isSupportedEncryptionAlgorithm(@EncryptionAlgorithm int algorithm) {
+        return SUPPORTED_ENCRYPTION_ALGORITHM.contains(algorithm);
+    }
+
+    // TODO: Implement constructing SaProposal with a Builder that supports adding
+    // encryption/integrity algorithms, prf, and DH Group.
+}
diff --git a/src/java/com/android/ike/ikev2/message/IkeSaPayload.java b/src/java/com/android/ike/ikev2/message/IkeSaPayload.java
index e3ded36..79a874b 100644
--- a/src/java/com/android/ike/ikev2/message/IkeSaPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeSaPayload.java
@@ -16,9 +16,13 @@
 
 package com.android.ike.ikev2.message;
 
+import static com.android.ike.ikev2.SaProposal.EncryptionAlgorithm;
+
 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.internal.annotations.VisibleForTesting;
@@ -28,6 +32,7 @@
 import java.nio.ByteBuffer;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * IkeSaPayload represents a Security Association payload. It contains one or more {@link Proposal}.
@@ -38,11 +43,11 @@
 public final class IkeSaPayload extends IkePayload {
     public final List<Proposal> proposalList;
     /**
-     * Construct an instance of IkeSaPayload in the context of IkePayloadFactory
+     * Construct an instance of IkeSaPayload in the context of IkePayloadFactory.
      *
      * @param critical indicates if this payload is critical. Ignored in supported payload as
      *     instructed by the RFC 7296.
-     * @param payloadBody the encoded payload body in byte array
+     * @param payloadBody the encoded payload body in byte array.
      */
     IkeSaPayload(boolean critical, byte[] payloadBody) throws IkeException {
         super(IkePayload.PAYLOAD_TYPE_SA, critical);
@@ -63,12 +68,14 @@
     // TODO: Add another constructor for building outbound message.
 
     /**
-     * Proposal represents a set contains cryptograhic algorithms and key generating materials. It
+     * 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).
-     *     <p>Ignore Proposal with unsupported Protocol ID when processing IkeSaPayload
+     *     <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;
@@ -83,7 +90,9 @@
                         Transform[] transformArray = new Transform[count];
                         for (int i = 0; i < count; i++) {
                             Transform transform = Transform.readFrom(inputBuffer);
-                            transformArray[i] = transform;
+                            if (transform.isSupported) {
+                                transformArray[i] = transform;
+                            }
                         }
                         return transformArray;
                     }
@@ -95,7 +104,9 @@
 
         public final byte spiSize;
         public final long spi;
+
         public final Transform[] transformArray;
+        // TODO: Validate this proposal
 
         @VisibleForTesting
         Proposal(byte number, int protocolId, byte spiSize, long spi, Transform[] transformArray) {
@@ -155,13 +166,15 @@
     }
 
     /**
-     * Transform represents a cryptograhic algorithm. It may contain one or more {@link Attribute}.
+     * 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).
-     *     <p>Ignore Transform with unsupported type when processing IkeSaPayload
+     *     <p>Transforms with unrecognized Transform ID or containing unrecognized Attribute Type
+     *     shall be ignored when processing received SA payload.
      */
-    public static final class Transform {
+    public abstract static class Transform {
 
         @Retention(RetentionPolicy.SOURCE)
         @IntDef({
@@ -204,12 +217,25 @@
         // Only supported type falls into {@link TransformType}
         public final int type;
         public final int id;
-        public final List<Attribute> attributeList;
+        public final boolean isSupported;
 
-        Transform(int type, int id, List<Attribute> attributeList) {
+        /** Construct an instance of Transform in the context of {@link SaProposal} */
+        protected Transform(int type, int id) {
             this.type = type;
             this.id = id;
-            this.attributeList = attributeList;
+            if (!isSupportedTransformId(id)) {
+                throw new IllegalArgumentException(
+                        "Unsupported " + getTransformTypeString() + " Algorithm ID: " + id);
+            }
+            this.isSupported = true;
+        }
+
+        /** Construct an instance of Transform in the context of {@link Transform} */
+        protected Transform(int type, int id, List<Attribute> attributeList) {
+            this.type = type;
+            this.id = id;
+            this.isSupported =
+                    isSupportedTransformId(id) && !hasUnrecognizedAttribute(attributeList);
         }
 
         @VisibleForTesting
@@ -234,14 +260,210 @@
             // Decode attributes
             List<Attribute> attributeList = sAttributeDecoder.decodeAttributes(length, inputBuffer);
 
-            return new Transform(type, id, attributeList);
+            validateAttributeUniqueness(attributeList);
+
+            switch (type) {
+                case TRANSFORM_TYPE_ENCR:
+                    return new EncryptionTransform(id, attributeList);
+                    // TODO: Add Integrity algorithm, PRF, DhGroup and ESN
+                default:
+                    return new UnrecognizedTransform(type, id, attributeList);
+            }
         }
 
-        // TODO: Add another contructor for encoding.
+        // 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 this Transform ID is supported.
+        protected abstract boolean isSupportedTransformId(int id);
+
+        // Check if there is Attribute with unrecognized type.
+        protected boolean hasUnrecognizedAttribute(List<Attribute> attributeList) {
+            for (Attribute attr : attributeList) {
+                if (attr instanceof UnrecognizedAttribute) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * 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
+    }
+
+    // TODO: Implement PrfTransform, IntegrityTransform, DhGroupTransform and EsnTransForm
+
+    /**
+     * 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).
+     */
+    public static final class EncryptionTransform extends Transform {
+        private static final int KEY_LEN_UNASSIGNED = 0;
+
+        public final int keyLength;
+
+        @Override
+        protected boolean isSupportedTransformId(int id) {
+            return SaProposal.isSupportedEncryptionAlgorithm(id);
+        }
+
+        /**
+         * Contruct an instance of EncryptionTransform in the context of {@link SaProposal} with
+         * fixed key length.
+         *
+         * @param id the IKE standard Transform ID.
+         */
+        public EncryptionTransform(@EncryptionAlgorithm int id) {
+            this(id, KEY_LEN_UNASSIGNED);
+        }
+
+        /**
+         * Contruct an instance of EncryptionTransform in the context of {@link SaProposal} with
+         * variable key length.
+         *
+         * @param id the IKE standard Transform ID.
+         * @param keyLength the specified key length of this encryption algorithm.
+         */
+        public EncryptionTransform(@EncryptionAlgorithm int id, int keyLength) {
+            super(Transform.TRANSFORM_TYPE_ENCR, id);
+
+            this.keyLength = keyLength;
+            try {
+                validateKeyLength();
+            } catch (InvalidSyntaxException e) {
+                throw new IllegalArgumentException(e.message);
+            }
+        }
+
+        /**
+         * Contruct an instance of EncryptionTransform in the context of abstract class {@link
+         * Transform}.
+         *
+         * @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) {
+                keyLength = KEY_LEN_UNASSIGNED;
+            } else {
+                if (attributeList.size() == 0) {
+                    keyLength = KEY_LEN_UNASSIGNED;
+                } else {
+                    KeyLengthAttribute attr = getKeyLengthAttribute(attributeList);
+                    keyLength = attr.keyLength;
+                }
+                validateKeyLength();
+            }
+        }
+
+        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 (keyLength != KEY_LEN_UNASSIGNED) {
+                        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 (keyLength == KEY_LEN_UNASSIGNED) {
+                        throw new InvalidSyntaxException(
+                                "Must set Key Length value for this "
+                                        + getTransformTypeString()
+                                        + " Algorithm ID: "
+                                        + id);
+                    }
+                    if (keyLength != SaProposal.KEY_LEN_AES_128
+                            && keyLength != SaProposal.KEY_LEN_AES_192
+                            && keyLength != 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
+        public String getTransformTypeString() {
+            return "Encryption Algorithm";
+        }
     }
 
     /**
-     * Attribute is for completing the specification of some {@link Transform}.
+     * UnrecognizedTransform represents a Transform with unrecognized Transform Type.
+     *
+     * <p>Proposals containing an UnrecognizedTransform should be ignored.
+     */
+    protected static final class UnrecognizedTransform extends Transform {
+
+        @Override
+        protected boolean isSupportedTransformId(int id) {
+            return false;
+        }
+
+        protected UnrecognizedTransform(int type, int id, List<Attribute> attributeList) {
+            super(type, id, attributeList);
+        }
+
+        /**
+         * 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,
@@ -252,7 +474,7 @@
      * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.5">RFC 7296, Internet Key
      *     Exchange Protocol Version 2 (IKEv2).
      */
-    public static final class Attribute {
+    public abstract static class Attribute {
         @Retention(RetentionPolicy.SOURCE)
         @IntDef({ATTRIBUTE_TYPE_KEY_LENGTH})
         public @interface AttributeType {}
@@ -265,17 +487,17 @@
 
         // Only Key Length type belongs to AttributeType
         public final int type;
-        public final byte[] value;
 
-        Attribute(int type, byte[] value) {
+        /** Construct an instance of an Attribute when decoding message. */
+        protected Attribute(int type) {
             this.type = type;
-            this.value = value;
         }
 
         @VisibleForTesting
         static Pair<Attribute, Integer> readFrom(ByteBuffer inputBuffer) throws IkeException {
             short formatAndType = inputBuffer.getShort();
             int type = formatAndType & 0x7fff;
+
             int length = 0;
             byte[] value = new byte[0];
             if ((formatAndType & 0x8000) == 0x8000) {
@@ -290,12 +512,41 @@
                 length = Short.toUnsignedInt(inputBuffer.getShort());
                 value = new byte[length - LENGTH_FOR_TV];
             }
+
             inputBuffer.get(value);
-            return new Pair(new Attribute(type, value), length);
+
+            switch (type) {
+                case ATTRIBUTE_TYPE_KEY_LENGTH:
+                    return new Pair(new KeyLengthAttribute(value), length);
+                default:
+                    return new Pair(new UnrecognizedAttribute(type, value), length);
+            }
+        }
+    }
+
+    /** 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()));
         }
 
-        // TODO: Add another contructor for encoding.
+        protected KeyLengthAttribute(int keyLength) {
+            super(ATTRIBUTE_TYPE_KEY_LENGTH);
+            this.keyLength = keyLength;
+        }
+    }
 
+    /**
+     * 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);
+        }
     }
 
     /**
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeKePayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeKePayloadTest.java
index 20deb8a..7d38d5c 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeKePayloadTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeKePayloadTest.java
@@ -50,7 +50,6 @@
     @IkePayload.PayloadType
     private static final int NEXT_PAYLOAD_TYPE = IkePayload.PAYLOAD_TYPE_NONCE;
 
-    @IkeKePayload.DhGroup
     private static final int EXPECTED_DH_GROUP = IkePayload.DH_GROUP_1024_BIT_MODP;
 
     private static final int EXPECTED_KE_DATA_LEN = 128;
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSaPayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSaPayloadTest.java
index 2ee9d3b..e2909fc 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSaPayloadTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSaPayloadTest.java
@@ -17,6 +17,9 @@
 package com.android.ike.ikev2.message;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.mock;
@@ -24,8 +27,20 @@
 
 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.message.IkeSaPayload.Attribute;
+import com.android.ike.ikev2.message.IkeSaPayload.AttributeDecoder;
+import com.android.ike.ikev2.message.IkeSaPayload.EncryptionTransform;
+import com.android.ike.ikev2.message.IkeSaPayload.KeyLengthAttribute;
+import com.android.ike.ikev2.message.IkeSaPayload.Proposal;
+import com.android.ike.ikev2.message.IkeSaPayload.Transform;
+import com.android.ike.ikev2.message.IkeSaPayload.TransformDecoder;
+import com.android.ike.ikev2.message.IkeSaPayload.UnrecognizedAttribute;
+import com.android.ike.ikev2.message.IkeSaPayload.UnrecognizedTransform;
 
+import org.junit.Before;
 import org.junit.Test;
 
 import java.nio.ByteBuffer;
@@ -36,6 +51,7 @@
     private static final String PROPOSAL_RAW_PACKET =
             "0000002c010100040300000c0100000c800e0080030000080300000203000008040"
                     + "000020000000802000002";
+
     private static final String TWO_PROPOSAL_RAW_PACKET =
             "020000dc010100190300000c0100000c800e00800300000c0100000c800e00c0030"
                     + "0000c0100000c800e01000300000801000003030000080300000c0300"
@@ -54,7 +70,10 @@
                     + "0300000804000015030000080400001c030000080400001d030000080"
                     + "400001e030000080400001f030000080400000f030000080400001003"
                     + "00000804000012000000080400000e";
-    private static final String TRANSFORM_RAW_PACKET = "0300000c0100000c800e0080";
+    private static final String ENCR_TRANSFORM_RAW_PACKET = "0300000c0100000c800e0080";
+    private static final int TRANSFORM_TYPE_POSITION = 4;
+    private static final int TRANSFORM_ID_POSITION = 7;
+
     private static final String ATTRIBUTE_RAW_PACKET = "800e0080";
 
     private static final int PROPOSAL_NUMBER = 1;
@@ -68,53 +87,161 @@
     // Constants for multiple proposals test
     private static final byte[] PROPOSAL_NUMBER_LIST = {1, 2};
 
-    private static final byte TRANSFORM_TYPE = 1;
-    private static final byte TRANSFORM_ID = 12;
+    private static final int KEY_LEN = 128;
 
-    private static final byte ATTRIBUTE_TYPE = 14;
-    private static final byte[] ATTRIBUTE_VALUE = {(byte) 0x00, (byte) 0x80};
+    private AttributeDecoder mMockedAttributeDecoder;
+    private KeyLengthAttribute mAttributeKeyLength128;
+    private List<Attribute> mAttributeListWithKeyLength128;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockedAttributeDecoder = mock(AttributeDecoder.class);
+        mAttributeKeyLength128 = new KeyLengthAttribute(SaProposal.KEY_LEN_AES_128);
+        mAttributeListWithKeyLength128 = new LinkedList<>();
+        mAttributeListWithKeyLength128.add(mAttributeKeyLength128);
+    }
 
     @Test
     public void testDecodeAttribute() throws Exception {
         byte[] inputPacket = TestUtils.hexStringToByteArray(ATTRIBUTE_RAW_PACKET);
         ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
 
-        Pair<IkeSaPayload.Attribute, Integer> pair = IkeSaPayload.Attribute.readFrom(inputBuffer);
-        IkeSaPayload.Attribute attribute = pair.first;
+        Pair<Attribute, Integer> pair = Attribute.readFrom(inputBuffer);
+        Attribute attribute = pair.first;
 
-        assertEquals(ATTRIBUTE_TYPE, attribute.type);
-        assertEquals(ATTRIBUTE_VALUE.length, attribute.value.length);
-        for (int i = 0; i < ATTRIBUTE_VALUE.length; i++) {
-            assertEquals(ATTRIBUTE_VALUE[i], attribute.value[i]);
+        assertEquals(Attribute.ATTRIBUTE_TYPE_KEY_LENGTH, attribute.type);
+        assertEquals(KEY_LEN, ((KeyLengthAttribute) attribute).keyLength);
+    }
+
+    @Test
+    public void testDecodeEncryptionTransform() throws Exception {
+        byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+        ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+        when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+                .thenReturn(mAttributeListWithKeyLength128);
+        Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+        Transform transform = Transform.readFrom(inputBuffer);
+
+        assertEquals(Transform.TRANSFORM_TYPE_ENCR, transform.type);
+        assertEquals(SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, transform.id);
+        assertTrue(transform.isSupported);
+    }
+
+    @Test
+    public void testDecodeEncryptionTransformWithInvalidKeyLength() throws Exception {
+        byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+        ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+        List<Attribute> attributeList = new LinkedList<>();
+        Attribute keyLengAttr = new KeyLengthAttribute(SaProposal.KEY_LEN_AES_128 + 1);
+        attributeList.add(keyLengAttr);
+
+        when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())).thenReturn(attributeList);
+        Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+        try {
+            Transform.readFrom(inputBuffer);
+            fail("Expected InvalidSyntaxException for invalid key length.");
+        } catch (InvalidSyntaxException expected) {
         }
     }
 
     @Test
-    public void testDecodeTransform() throws Exception {
-        byte[] inputPacket = TestUtils.hexStringToByteArray(TRANSFORM_RAW_PACKET);
+    public void testConstructEncryptionTransformWithUnSupportedId() throws Exception {
+        try {
+            new EncryptionTransform(SaProposal.ENCRYPTION_ALGORITHM_3DES + 1);
+            fail("Expected IllegalArgumentException for unsupported Transform ID");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testConstructEncryptionTransformWithInvalidKeyLength() throws Exception {
+        try {
+            new EncryptionTransform(SaProposal.ENCRYPTION_ALGORITHM_3DES, 129);
+            fail("Expected IllegalArgumentException for invalid key length.");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testDecodeUnrecognizedTransform() throws Exception {
+        byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+        inputPacket[TRANSFORM_TYPE_POSITION] = 6;
         ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
-        IkeSaPayload.AttributeDecoder mockedDecoder = mock(IkeSaPayload.AttributeDecoder.class);
-        List<IkeSaPayload.Attribute> attributeList = new LinkedList<>();
-        when(mockedDecoder.decodeAttributes(anyInt(), any())).thenReturn(attributeList);
-        IkeSaPayload.Transform.sAttributeDecoder = mockedDecoder;
 
-        IkeSaPayload.Transform transform = IkeSaPayload.Transform.readFrom(inputBuffer);
+        when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+                .thenReturn(mAttributeListWithKeyLength128);
+        Transform.sAttributeDecoder = mMockedAttributeDecoder;
 
-        assertEquals(TRANSFORM_TYPE, transform.type);
-        assertEquals(TRANSFORM_ID, transform.id);
-        assertEquals(0, transform.attributeList.size());
+        Transform transform = Transform.readFrom(inputBuffer);
+
+        assertEquals(UnrecognizedTransform.class, transform.getClass());
+    }
+
+    @Test
+    public void testDecodeTransformWithRepeatedAttribute() throws Exception {
+        byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+        ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+        List<Attribute> attributeList = new LinkedList<>();
+        attributeList.add(mAttributeKeyLength128);
+        attributeList.add(mAttributeKeyLength128);
+
+        when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())).thenReturn(attributeList);
+        Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+        try {
+            Transform.readFrom(inputBuffer);
+            fail("Expected InvalidSyntaxException for repeated Attribute Type Key Length.");
+        } catch (InvalidSyntaxException expected) {
+        }
+    }
+
+    @Test
+    public void testDecodeTransformWithUnrecognizedTransformId() throws Exception {
+        byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+        inputPacket[TRANSFORM_ID_POSITION] = 1;
+        ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+        when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+                .thenReturn(mAttributeListWithKeyLength128);
+        Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+        Transform transform = Transform.readFrom(inputBuffer);
+
+        assertFalse(transform.isSupported);
+    }
+
+    @Test
+    public void testDecodeTransformWithUnrecogniedAttributeType() throws Exception {
+        byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+        ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+        List<Attribute> attributeList = new LinkedList<>();
+        attributeList.add(mAttributeKeyLength128);
+        Attribute attributeUnrecognized = new UnrecognizedAttribute(1, new byte[0]);
+        attributeList.add(attributeUnrecognized);
+
+        when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())).thenReturn(attributeList);
+        Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+        Transform transform = Transform.readFrom(inputBuffer);
+
+        assertFalse(transform.isSupported);
     }
 
     @Test
     public void testDecodeSingleProposal() throws Exception {
         byte[] inputPacket = TestUtils.hexStringToByteArray(PROPOSAL_RAW_PACKET);
         ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
-        IkeSaPayload.TransformDecoder mockedDecoder = mock(IkeSaPayload.TransformDecoder.class);
-        when(mockedDecoder.decodeTransforms(anyInt(), any()))
-                .thenReturn(new IkeSaPayload.Transform[0]);
-        IkeSaPayload.Proposal.sTransformDecoder = mockedDecoder;
+        TransformDecoder mockedDecoder = mock(TransformDecoder.class);
+        when(mockedDecoder.decodeTransforms(anyInt(), any())).thenReturn(new Transform[0]);
+        Proposal.sTransformDecoder = mockedDecoder;
 
-        IkeSaPayload.Proposal proposal = IkeSaPayload.Proposal.readFrom(inputBuffer);
+        Proposal proposal = Proposal.readFrom(inputBuffer);
 
         assertEquals(PROPOSAL_NUMBER, proposal.number);
         assertEquals(PROPOSAL_PROTOCOL_ID, proposal.protocolId);
@@ -126,11 +253,11 @@
     @Test
     public void testDecodeMultipleProposal() throws Exception {
         byte[] inputPacket = TestUtils.hexStringToByteArray(TWO_PROPOSAL_RAW_PACKET);
-        IkeSaPayload.Proposal.sTransformDecoder =
-                new IkeSaPayload.TransformDecoder() {
+        Proposal.sTransformDecoder =
+                new TransformDecoder() {
                     @Override
-                    public IkeSaPayload.Transform[] decodeTransforms(
-                            int count, ByteBuffer inputBuffer) throws IkeException {
+                    public Transform[] decodeTransforms(int count, ByteBuffer inputBuffer)
+                            throws IkeException {
                         for (int i = 0; i < count; i++) {
                             // Read length field and move position
                             inputBuffer.getShort();
@@ -138,7 +265,7 @@
                             byte[] temp = new byte[length - 4];
                             inputBuffer.get(temp);
                         }
-                        return new IkeSaPayload.Transform[0];
+                        return new Transform[0];
                     }
                 };
 
@@ -146,7 +273,7 @@
 
         assertEquals(PROPOSAL_NUMBER_LIST.length, payload.proposalList.size());
         for (int i = 0; i < payload.proposalList.size(); i++) {
-            IkeSaPayload.Proposal proposal = payload.proposalList.get(i);
+            Proposal proposal = payload.proposalList.get(i);
             assertEquals(PROPOSAL_NUMBER_LIST[i], proposal.number);
             assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId);
             assertEquals(0, proposal.spiSize);