Decode and validate IKE fragment in IkeMessage

Bug: 122677075
Test: atest frameworksIkeTests (new tests added)
Change-Id: Ib1a2a624804a67597429fab2fb35fdb50e29bffc
diff --git a/src/java/com/android/ike/ikev2/IkeManager.java b/src/java/com/android/ike/ikev2/IkeManager.java
index ab37429..61a67de 100644
--- a/src/java/com/android/ike/ikev2/IkeManager.java
+++ b/src/java/com/android/ike/ikev2/IkeManager.java
@@ -66,8 +66,8 @@
                 firstChildSessionCallback);
     }
 
-    // Package private
-    static Log getIkeLog() {
+    /** Returns IKE logger. */
+    public static Log getIkeLog() {
         return sIkeLog;
     }
 
diff --git a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
index a90d4df..0ad180c 100644
--- a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
+++ b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
@@ -1364,7 +1364,8 @@
                                 mIkeCipher,
                                 ikeSaRecord,
                                 ikeHeader,
-                                ikePacketBytes);
+                                ikePacketBytes,
+                                null /*collectedFragments*/);
                 switch (decodeResult.status) {
                     case DECODE_STATUS_OK:
                         ikeSaRecord.incrementLocalRequestMessageId();
@@ -1425,7 +1426,8 @@
                                     mIkeCipher,
                                     ikeSaRecord,
                                     ikeHeader,
-                                    ikePacketBytes);
+                                    ikePacketBytes,
+                                    null /*collectedFragments*/);
                     switch (decodeResult.status) {
                         case DECODE_STATUS_OK:
                             ikeSaRecord.incrementRemoteRequestMessageId();
@@ -3636,7 +3638,8 @@
                                         mIkeCipher,
                                         ikeSaRecord,
                                         ikeHeader,
-                                        receivedIkePacket.ikePacketBytes);
+                                        receivedIkePacket.ikePacketBytes,
+                                        null /*collectedFragments*/);
                         isMessageOnNewSa =
                                 (decodeResult.status == DECODE_STATUS_PROTECTED_ERROR)
                                         || (decodeResult.status == DECODE_STATUS_OK);
diff --git a/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java b/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java
index 6967efd..eec75be 100644
--- a/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java
+++ b/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java
@@ -60,7 +60,7 @@
             IkeMacIntegrity integrityMac,
             IkeCipher decryptCipher,
             byte[] integrityKey,
-            byte[] decryptKey)
+            byte[] decryptionKey)
             throws IkeProtocolException, GeneralSecurityException {
         ByteBuffer inputBuffer = ByteBuffer.wrap(message);
 
@@ -82,7 +82,7 @@
         // Authenticate and decrypt.
         byte[] dataToAuthenticate = Arrays.copyOfRange(message, 0, message.length - checksumLen);
         validateChecksumOrThrow(dataToAuthenticate, integrityMac, integrityKey, mIntegrityChecksum);
-        mUnencryptedData = decrypt(mEncryptedAndPaddedData, decryptCipher, decryptKey, mIv);
+        mUnencryptedData = decrypt(mEncryptedAndPaddedData, decryptCipher, decryptionKey, mIv);
     }
 
     /**
@@ -204,9 +204,9 @@
     /** Package private for testing */
     @VisibleForTesting
     static byte[] decrypt(
-            byte[] encryptedData, IkeCipher decryptCipher, byte[] decryptKey, byte[] iv)
+            byte[] encryptedData, IkeCipher decryptCipher, byte[] decryptionKey, byte[] iv)
             throws IllegalBlockSizeException {
-        byte[] decryptedPaddedData = decryptCipher.decrypt(encryptedData, decryptKey, iv);
+        byte[] decryptedPaddedData = decryptCipher.decrypt(encryptedData, decryptionKey, iv);
 
         // Remove padding. Pad length value is the last byte of the padded unencrypted data.
         int padLength = Byte.toUnsignedInt(decryptedPaddedData[encryptedData.length - 1]);
diff --git a/src/java/com/android/ike/ikev2/message/IkeHeader.java b/src/java/com/android/ike/ikev2/message/IkeHeader.java
index 188a8b0..bf9dc3e 100644
--- a/src/java/com/android/ike/ikev2/message/IkeHeader.java
+++ b/src/java/com/android/ike/ikev2/message/IkeHeader.java
@@ -155,8 +155,8 @@
         return mEncodedMessageLength;
     }
 
-    /** Validate syntax and major version of inbound IKE header. */
-    public void checkInboundValidOrThrow(int packetLength) throws IkeProtocolException {
+    /** Validate major version of inbound IKE header. */
+    public void validateMajorVersion() throws IkeProtocolException {
         if (majorVersion > 2) {
             // Receive higher version of protocol. Stop parsing.
             throw new InvalidMajorVersionException(majorVersion);
@@ -168,6 +168,15 @@
             // error.
             throw new InvalidSyntaxException("Major version is smaller than 2.");
         }
+    }
+
+    /**
+     * Validate syntax of inbound IKE header.
+     *
+     * <p>It MUST only be used for an inbound IKE header because we don't know the outbound message
+     * length before we encode it.
+     */
+    public void validateInboundHeader(int packetLength) throws IkeProtocolException {
         if (exchangeType < EXCHANGE_TYPE_IKE_SA_INIT
                 || exchangeType > EXCHANGE_TYPE_INFORMATIONAL) {
             throw new InvalidSyntaxException("Invalid IKE Exchange Type.");
diff --git a/src/java/com/android/ike/ikev2/message/IkeMessage.java b/src/java/com/android/ike/ikev2/message/IkeMessage.java
index b8eff9a..c3323c2 100644
--- a/src/java/com/android/ike/ikev2/message/IkeMessage.java
+++ b/src/java/com/android/ike/ikev2/message/IkeMessage.java
@@ -16,6 +16,7 @@
 
 package com.android.ike.ikev2.message;
 
+import static com.android.ike.ikev2.IkeManager.getIkeLog;
 import static com.android.ike.ikev2.message.IkePayload.PayloadType;
 
 import android.annotation.IntDef;
@@ -56,6 +57,8 @@
  *     Protocol Version 2 (IKEv2)</a>
  */
 public final class IkeMessage {
+    private static final String TAG = "IkeMessage";
+
     private static IIkeMessageHelper sIkeMessageHelper = new IkeMessageHelper();
     // Currently use Bouncy Castle as crypto security provider
     static final Provider SECURITY_PROVIDER = new BouncyCastleProvider();
@@ -120,6 +123,7 @@
      * @param ikeSaRecord ikeSaRecord where this packet is sent on.
      * @param ikeHeader header of IKE packet.
      * @param packet IKE packet as a byte array.
+     * @param collectedFragments previously received IKE fragments.
      * @return the decoding result.
      */
     public static DecodeResult decode(
@@ -128,9 +132,16 @@
             IkeCipher decryptCipher,
             IkeSaRecord ikeSaRecord,
             IkeHeader ikeHeader,
-            byte[] packet) {
+            byte[] packet,
+            DecodeResultPartial collectedFragments) {
         return sIkeMessageHelper.decode(
-                expectedMsgId, integrityMac, decryptCipher, ikeSaRecord, ikeHeader, packet);
+                expectedMsgId,
+                integrityMac,
+                decryptCipher,
+                ikeSaRecord,
+                ikeHeader,
+                packet,
+                collectedFragments);
     }
 
     private static List<IkePayload> decodePayloadList(
@@ -359,6 +370,7 @@
          * @param ikeSaRecord ikeSaRecord where this packet is sent on.
          * @param ikeHeader header of IKE packet.
          * @param packet IKE packet as a byte array.
+         * @param collectedFragments previously received IKE fragments.
          * @return the decoding result.
          */
         DecodeResult decode(
@@ -367,7 +379,8 @@
                 IkeCipher decryptCipher,
                 IkeSaRecord ikeSaRecord,
                 IkeHeader ikeHeader,
-                byte[] packet);
+                byte[] packet,
+                DecodeResultPartial collectedFragments);
     }
 
     /** IkeMessageHelper provides methods for decoding, encoding and processing IKE packet. */
@@ -429,7 +442,8 @@
                     throw new InvalidMessageIdException(header.messageId);
                 }
 
-                header.checkInboundValidOrThrow(inputPacket.length);
+                header.validateMajorVersion();
+                header.validateInboundHeader(inputPacket.length);
 
                 byte[] unencryptedPayloads =
                         Arrays.copyOfRange(
@@ -455,7 +469,8 @@
                 IkeCipher decryptCipher,
                 IkeSaRecord ikeSaRecord,
                 IkeHeader ikeHeader,
-                byte[] packet) {
+                byte[] packet,
+                DecodeResultPartial collectedFragments) {
             return decode(
                     expectedMsgId,
                     ikeHeader,
@@ -463,7 +478,8 @@
                     integrityMac,
                     decryptCipher,
                     ikeSaRecord.getInboundIntegrityKey(),
-                    ikeSaRecord.getInboundDecryptionKey());
+                    ikeSaRecord.getInboundDecryptionKey(),
+                    collectedFragments);
         }
 
         private DecodeResult decode(
@@ -473,62 +489,195 @@
                 @Nullable IkeMacIntegrity integrityMac,
                 IkeCipher decryptCipher,
                 byte[] integrityKey,
-                byte[] decryptKey) {
-
-            if (header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SK) {
+                byte[] decryptionKey,
+                DecodeResultPartial collectedFragments) {
+            if (header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SK
+                    && header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SKF) {
                 // TODO: b/123372339 Handle message containing unprotected payloads.
                 throw new UnsupportedOperationException("Message contains unprotected payloads");
             }
 
-            // Validate security parameters.
+            // Decrypt message and do authentication
             Pair<IkeSkPayload, Integer> pair;
             try {
-                if (header.messageId != expectedMsgId) {
-                    throw new InvalidMessageIdException(header.messageId);
-                }
                 pair =
-                        IkePayloadFactory.getIkeSkPayload(
-                                false /*isSkf*/,
+                        decryptAndAuthenticate(
+                                expectedMsgId,
+                                header,
                                 inputPacket,
                                 integrityMac,
                                 decryptCipher,
                                 integrityKey,
-                                decryptKey);
-
-                // TODO: Support decoding IkeSkfPayload
-            } catch (NegativeArraySizeException | BufferUnderflowException e) {
-                return new DecodeResultError(
-                        DECODE_STATUS_UNPROTECTED_ERROR,
-                        new InvalidSyntaxException("Malformed IKE Payload"));
-            } catch (GeneralSecurityException e) {
-                return new DecodeResultError(
-                        DECODE_STATUS_UNPROTECTED_ERROR, new IkeInternalException(e));
+                                decryptionKey);
             } catch (IkeException e) {
-                return new DecodeResultError(DECODE_STATUS_UNPROTECTED_ERROR, e);
+                if (collectedFragments == null) {
+                    return new DecodeResultError(DECODE_STATUS_UNPROTECTED_ERROR, e);
+                } else {
+                    getIkeLog()
+                            .i(
+                                    TAG,
+                                    "Message authentication or decryption failed on received"
+                                            + " message. Discard it ",
+                                    e);
+                    return collectedFragments;
+                }
             }
 
-            // Check is there is protocol error in this IKE message.
+            // Handle IKE fragment
+            boolean isFragment = (header.nextPayloadType == IkePayload.PAYLOAD_TYPE_SKF);
+            boolean fragReassemblyStarted = (collectedFragments != null);
+
+            if (isFragment) {
+                getIkeLog()
+                        .d(
+                                TAG,
+                                "Received an IKE fragment ("
+                                        + ((IkeSkfPayload) pair.first).fragmentNum
+                                        + "/"
+                                        + ((IkeSkfPayload) pair.first).totalFragments
+                                        + ")");
+            }
+
+            // IKE fragment reassembly has started but a complete message was received.
+            if (!isFragment && fragReassemblyStarted) {
+                getIkeLog()
+                        .w(
+                                TAG,
+                                "Received a complete IKE message while doing IKE fragment"
+                                        + " reassembly. Discard the newly received message.");
+                return collectedFragments;
+            }
+
+            byte[] decryptedBytes = pair.first.getUnencryptedData();
+            int firstPayloadType = pair.second;
+
+            // Received an IKE fragment
+            if (isFragment) {
+                validateFragmentHeader(header, inputPacket.length, collectedFragments);
+
+                // Add the recently received fragment to the reassembly queue.
+                DecodeResultPartial DecodeResultPartial =
+                        processIkeFragment(
+                                header,
+                                (IkeSkfPayload) (pair.first),
+                                pair.second,
+                                collectedFragments);
+
+                if (!DecodeResultPartial.isAllFragmentsReceived()) return DecodeResultPartial;
+
+                firstPayloadType = DecodeResultPartial.firstPayloadType;
+                decryptedBytes = DecodeResultPartial.reassembleAllFrags();
+            }
+
+            // Received or has reassembled a complete IKE message. Check if there is protocol error.
             try {
-                IkeSkPayload skPayload = pair.first;
-                int firstPayloadType = pair.second;
+                // TODO: Log IKE header information and payload types
 
                 List<IkePayload> supportedPayloadList =
-                        decodePayloadList(
-                                firstPayloadType,
-                                header.isResponseMsg,
-                                skPayload.getUnencryptedData());
+                        decodePayloadList(firstPayloadType, header.isResponseMsg, decryptedBytes);
 
-                header.checkInboundValidOrThrow(inputPacket.length);
+                header.validateInboundHeader(inputPacket.length);
                 return new DecodeResultOk(new IkeMessage(header, supportedPayloadList));
             } catch (NegativeArraySizeException | BufferUnderflowException e) {
                 // Invalid length error when parsing payload bodies.
                 return new DecodeResultError(
                         DECODE_STATUS_PROTECTED_ERROR,
-                        new InvalidSyntaxException("Malformed IKE Payload"));
+                        new InvalidSyntaxException("Malformed IKE Payload", e));
             } catch (IkeProtocolException e) {
                 return new DecodeResultError(DECODE_STATUS_PROTECTED_ERROR, e);
             }
         }
+
+        private Pair<IkeSkPayload, Integer> decryptAndAuthenticate(
+                int expectedMsgId,
+                IkeHeader header,
+                byte[] inputPacket,
+                @Nullable IkeMacIntegrity integrityMac,
+                IkeCipher decryptCipher,
+                byte[] integrityKey,
+                byte[] decryptionKey)
+                throws IkeException {
+
+            try {
+                if (header.messageId != expectedMsgId) {
+                    throw new InvalidMessageIdException(header.messageId);
+                }
+
+                header.validateMajorVersion();
+
+                boolean isSkf = header.nextPayloadType == IkePayload.PAYLOAD_TYPE_SKF;
+                return IkePayloadFactory.getIkeSkPayload(
+                        isSkf,
+                        inputPacket,
+                        integrityMac,
+                        decryptCipher,
+                        integrityKey,
+                        decryptionKey);
+            } catch (NegativeArraySizeException | BufferUnderflowException e) {
+                throw new InvalidSyntaxException("Malformed IKE Payload", e);
+            } catch (GeneralSecurityException e) {
+                throw new IkeInternalException(e);
+            }
+        }
+
+        private void validateFragmentHeader(
+                IkeHeader fragIkeHeader, int packetLen, DecodeResultPartial collectedFragments) {
+            try {
+                fragIkeHeader.validateInboundHeader(packetLen);
+            } catch (IkeProtocolException e) {
+                getIkeLog()
+                        .e(
+                                TAG,
+                                "Received an IKE fragment with invalid header. Will be handled when"
+                                        + " reassembly is done.",
+                                e);
+            }
+
+            if (collectedFragments == null) return;
+            if (fragIkeHeader.exchangeType != collectedFragments.ikeHeader.exchangeType) {
+                getIkeLog()
+                        .e(
+                                TAG,
+                                "Received an IKE fragment with different exchange type from"
+                                        + " previously collected fragments. Ignore it.");
+            }
+        }
+
+        private DecodeResultPartial processIkeFragment(
+                IkeHeader header,
+                IkeSkfPayload skf,
+                int nextPayloadType,
+                @Nullable DecodeResultPartial collectedFragments) {
+            if (collectedFragments == null) {
+                return new DecodeResultPartial(header, skf, nextPayloadType, collectedFragments);
+            }
+
+            if (skf.totalFragments > collectedFragments.collectedFragsList.length) {
+                getIkeLog()
+                        .i(
+                                TAG,
+                                "Received IKE fragment has larger total fragments number. Discard"
+                                        + " all previously collected fragments");
+                return new DecodeResultPartial(
+                        header, skf, nextPayloadType, null /*collectedFragments*/);
+            }
+
+            if (skf.totalFragments < collectedFragments.collectedFragsList.length) {
+                getIkeLog()
+                        .i(
+                                TAG,
+                                "Received IKE fragment has smaller total fragments number. Discard"
+                                        + " it.");
+                return collectedFragments;
+            }
+
+            if (collectedFragments.collectedFragsList[skf.fragmentNum - 1] != null) {
+                getIkeLog().i(TAG, "Received IKE fragment is a replay.");
+                return collectedFragments;
+            }
+
+            return new DecodeResultPartial(header, skf, nextPayloadType, collectedFragments);
+        }
     }
 
     /** Status to describe the result of decoding an inbound IKE message. */
diff --git a/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java b/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
index 74dd4c9..0e4290c 100644
--- a/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
+++ b/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.ike.ikev2.message;
 
+import android.annotation.Nullable;
 import android.util.Pair;
 
 import com.android.ike.ikev2.crypto.IkeCipher;
@@ -90,6 +91,35 @@
                     return new IkeUnsupportedPayload(payloadType, isCritical);
             }
         }
+
+        @Override
+        public IkeSkPayload decodeIkeSkPayload(
+                boolean isSkf,
+                boolean critical,
+                byte[] message,
+                @Nullable IkeMacIntegrity integrityMac,
+                IkeCipher decryptCipher,
+                byte[] integrityKey,
+                byte[] decryptionKey)
+                throws IkeProtocolException, GeneralSecurityException {
+            if (isSkf) {
+                return new IkeSkfPayload(
+                        critical,
+                        message,
+                        integrityMac,
+                        decryptCipher,
+                        integrityKey,
+                        decryptionKey);
+            } else {
+                return new IkeSkPayload(
+                        critical,
+                        message,
+                        integrityMac,
+                        decryptCipher,
+                        integrityKey,
+                        decryptionKey);
+            }
+        }
     }
 
     /**
@@ -134,7 +164,7 @@
      * @param integrityMac the negotiated integrity algorithm.
      * @param decryptCipher the negotiated encryption algorithm.
      * @param integrityKey the negotiated integrity algorithm key.
-     * @param decryptKey the negotiated decryption key.
+     * @param decryptionKey the negotiated decryption key.
      * @return a pair including IkePayload and next payload type.
      * @throws IkeProtocolException for decoding errors.
      * @throws GeneralSecurityException if there is any error during integrity check or decryption.
@@ -145,7 +175,7 @@
             IkeMacIntegrity integrityMac,
             IkeCipher decryptCipher,
             byte[] integrityKey,
-            byte[] decryptKey)
+            byte[] decryptionKey)
             throws IkeProtocolException, GeneralSecurityException {
         ByteBuffer input =
                 ByteBuffer.wrap(
@@ -174,26 +204,15 @@
                             + " or SK Payload is not the only payload.");
         }
 
-        IkeSkPayload payload = null;
-        if (isSkf) {
-            payload =
-                    new IkeSkfPayload(
-                            isCritical,
-                            message,
-                            integrityMac,
-                            decryptCipher,
-                            integrityKey,
-                            decryptKey);
-        } else {
-            payload =
-                    new IkeSkPayload(
-                            isCritical,
-                            message,
-                            integrityMac,
-                            decryptCipher,
-                            integrityKey,
-                            decryptKey);
-        }
+        IkeSkPayload payload =
+                sDecoderInstance.decodeIkeSkPayload(
+                        isSkf,
+                        isCritical,
+                        message,
+                        integrityMac,
+                        decryptCipher,
+                        integrityKey,
+                        decryptionKey);
 
         return new Pair(payload, nextPayloadType);
     }
@@ -209,5 +228,15 @@
         IkePayload decodeIkePayload(
                 int payloadType, boolean isCritical, boolean isResp, byte[] payloadBody)
                 throws IkeProtocolException;
+
+        IkeSkPayload decodeIkeSkPayload(
+                boolean isSkf,
+                boolean critical,
+                byte[] message,
+                @Nullable IkeMacIntegrity integrityMac,
+                IkeCipher decryptCipher,
+                byte[] integrityKey,
+                byte[] decryptionKey)
+                throws IkeProtocolException, GeneralSecurityException;
     }
 }
diff --git a/src/java/com/android/ike/ikev2/message/IkeSkPayload.java b/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
index 8a82936..aa780f9 100644
--- a/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
@@ -50,7 +50,7 @@
      * @param integrityMac the negotiated integrity algorithm.
      * @param decryptCipher the negotiated encryption algorithm.
      * @param integrityKey the negotiated integrity algorithm key.
-     * @param decryptKey the negotiated decryption key.
+     * @param decryptionKey the negotiated decryption key.
      */
     @VisibleForTesting
     IkeSkPayload(
@@ -59,7 +59,7 @@
             @Nullable IkeMacIntegrity integrityMac,
             IkeCipher decryptCipher,
             byte[] integrityKey,
-            byte[] decryptKey)
+            byte[] decryptionKey)
             throws IkeProtocolException, GeneralSecurityException {
 
         this(
@@ -70,7 +70,7 @@
                 integrityMac,
                 decryptCipher,
                 integrityKey,
-                decryptKey);
+                decryptionKey);
     }
 
     /** Construct an instance of IkeSkPayload for testing.*/
@@ -89,7 +89,7 @@
             @Nullable IkeMacIntegrity integrityMac,
             IkeCipher decryptCipher,
             byte[] integrityKey,
-            byte[] decryptKey)
+            byte[] decryptionKey)
             throws IkeProtocolException, GeneralSecurityException {
         super(isSkf ? PAYLOAD_TYPE_SKF : PAYLOAD_TYPE_SK, critical);
 
@@ -102,7 +102,7 @@
                         integrityMac,
                         decryptCipher,
                         integrityKey,
-                        decryptKey);
+                        decryptionKey);
     }
 
     /**
diff --git a/src/java/com/android/ike/ikev2/message/IkeSkfPayload.java b/src/java/com/android/ike/ikev2/message/IkeSkfPayload.java
index 93abe90..54f083f 100644
--- a/src/java/com/android/ike/ikev2/message/IkeSkfPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeSkfPayload.java
@@ -52,7 +52,7 @@
      * @param integrityMac the negotiated integrity algorithm.
      * @param decryptCipher the negotiated encryption algorithm.
      * @param integrityKey the negotiated integrity algorithm key.
-     * @param decryptKey the negotiated decryption key.
+     * @param decryptionKey the negotiated decryption key.
      */
     IkeSkfPayload(
             boolean critical,
@@ -60,7 +60,7 @@
             @Nullable IkeMacIntegrity integrityMac,
             IkeCipher decryptCipher,
             byte[] integrityKey,
-            byte[] decryptKey)
+            byte[] decryptionKey)
             throws IkeProtocolException, GeneralSecurityException {
         super(
                 true /*isSkf*/,
@@ -70,7 +70,7 @@
                 integrityMac,
                 decryptCipher,
                 integrityKey,
-                decryptKey);
+                decryptionKey);
 
         // TODO: Support constructing IkeEncryptedPayloadBody using AEAD.
 
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java
index f3941fb..5387a6f 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java
@@ -389,7 +389,8 @@
                         any(),
                         eq(ikeSaRecord),
                         eq(dummyIkeMessage.ikeHeader),
-                        eq(dummyIkePacketBytes)))
+                        eq(dummyIkePacketBytes),
+                        eq(null)))
                 .thenReturn(new DecodeResultOk(dummyIkeMessage));
 
         return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes);
@@ -415,8 +416,9 @@
                                 ? ikeSaRecord.getLocalRequestMessageId()
                                 : ikeSaRecord.getRemoteRequestMessageId());
         when(mMockIkeMessageHelper.decode(
-                        anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any()))
-                .thenReturn(new DecodeResultError(DECODE_STATUS_PROTECTED_ERROR, exception));
+                        anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any(), any()))
+                .thenReturn(
+                        new DecodeResultError(DECODE_STATUS_PROTECTED_ERROR, exception));
 
         return new ReceivedIkePacket(header, new byte[0]);
     }
@@ -462,7 +464,8 @@
                         any(),
                         eq(record),
                         eq(rcvPacket.ikeHeader),
-                        eq(rcvPacket.ikePacketBytes));
+                        eq(rcvPacket.ikePacketBytes),
+                        eq(null));
     }
 
     private static IkeSaRecord makeDummyIkeSaRecord(long initSpi, long respSpi, boolean isLocalInit)
@@ -1747,9 +1750,9 @@
             boolean hasChildPayloads)
             throws Exception {
         // Send IKE AUTH response to IKE state machine
+        ReceivedIkePacket authResp = makeIkeAuthRespWithChildPayloads(authRelatedPayloads);
         mIkeSessionStateMachine.sendMessage(
-                IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
-                makeIkeAuthRespWithChildPayloads(authRelatedPayloads));
+                IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, authResp);
         mLooper.dispatchAll();
 
         // Validate outbound IKE AUTH request
@@ -1765,7 +1768,7 @@
 
         // Validate inbound IKE AUTH response
         verifyIncrementLocaReqMsgId();
-        verify(mMockIkeMessageHelper).decode(anyInt(), any(), any(), any(), any(), any());
+        verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, authResp);
 
         // Validate authentication is done. Cannot use matchers because IkeAuthPskPayload is final.
         verify(spyAuthPayload)
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeMessageTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeMessageTest.java
index 36efa3a..bce248f 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeMessageTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeMessageTest.java
@@ -20,6 +20,7 @@
 import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_PROTECTED_ERROR;
 import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR;
 import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_AUTH;
+import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_ID_INITIATOR;
 import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_NO_NEXT;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -29,7 +30,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import com.android.ike.TestUtils;
@@ -38,7 +45,6 @@
 import com.android.ike.ikev2.crypto.IkeMacIntegrity;
 import com.android.ike.ikev2.exceptions.IkeException;
 import com.android.ike.ikev2.exceptions.IkeInternalException;
-import com.android.ike.ikev2.exceptions.IkeProtocolException;
 import com.android.ike.ikev2.exceptions.InvalidMessageIdException;
 import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
 import com.android.ike.ikev2.exceptions.UnsupportedCriticalPayloadException;
@@ -46,6 +52,7 @@
 import com.android.ike.ikev2.message.IkeMessage.DecodeResultError;
 import com.android.ike.ikev2.message.IkeMessage.DecodeResultOk;
 import com.android.ike.ikev2.message.IkeMessage.DecodeResultPartial;
+import com.android.ike.ikev2.message.IkePayloadFactory.IIkePayloadDecoder;
 
 import org.junit.After;
 import org.junit.Before;
@@ -114,6 +121,18 @@
                     + "29000008000040000000000c000040010000000100000000"
                     + "000000000000000b";
 
+    private static final String IKE_FRAG_HEX_STRING =
+            "939ae1251d18eb9077a99551b15c6e9335202320000000010000"
+                    + "00c0000000a400020002fd7c7931705af184b7be76bbd45a"
+                    + "8ecbb3ffd58b9438b93f67e9fe86b06229f80e9b52d2ff6a"
+                    + "fde3f2c13ae93ce55a801f62e1a818c9003880a36bbe986f"
+                    + "e6979ba233b9f4f0ddc992d06dbad5a2b998be18fae947e5"
+                    + "ccfb37775d069344e711fbf499bb289cf4cca245bd450ad8"
+                    + "9d18689207759507ba18d47247e920b9e000a25a7596e413"
+                    + "0929e5cdc37d5c1b0d90bbaae946c260f4d3cf815f6d";
+    private static final String ID_INIT_PAYLOAD_HEX_STRING = "2400000c010000000a50500d";
+    private static final String ID_RESP_PAYLOAD_HEX_STRING = "0000000c010000000a505050";
+
     private static final long INIT_SPI = 0x5f54bf6d8b48e6e1L;
     private static final long RESP_SPI = 0x909232b3d1edcb5cL;
     private static final String IKE_EMPTY_INFO_MSG_HEX_STRING =
@@ -134,6 +153,10 @@
     private static final int FRAGMENT_NUM_ONE = 1;
     private static final int FRAGMENT_NUM_TWO = 2;
 
+    private static final int IKE_FRAG_EXPECTED_MESSAGE_ID = 1;
+    private static final int TOTAL_FRAGMENTS_OFFSET =
+            IkeHeader.IKE_HEADER_LENGTH + IkePayload.GENERIC_HEADER_LENGTH + 2;
+
     private static final int IKE_AUTH_EXPECTED_MESSAGE_ID = 1;
     private static final int IKE_AUTH_CIPHER_IV_SIZE = 16;
     private static final int IKE_AUTH_CIPHER_BLOCK_SIZE = 16;
@@ -143,12 +166,15 @@
     private byte[] mUnencryptedPaddedData;
     private IkeHeader mIkeAuthHeader;
 
+    private IIkePayloadDecoder mSpyIkePayloadDecoder;
+
     private IkeMacIntegrity mMockIntegrity;
     private IkeCipher mMockCipher;
     private IkeSaRecord mMockIkeSaRecord;
 
-    private IkeHeader mMockFragOneHeader;
-    private IkeHeader mMockFragTwoHeader;
+    private byte[] mIkeFragPacket;
+    private IkeHeader mFragOneHeader;
+    private IkeHeader mFragTwoHeader;
 
     private IkeSkfPayload mDummySkfPayloadOne;
     private IkeSkfPayload mDummySkfPayloadTwo;
@@ -187,20 +213,20 @@
 
     @Before
     public void setUp() throws Exception {
-        IkePayloadFactory.sDecoderInstance =
-                new IkePayloadFactory.IIkePayloadDecoder() {
-
-                    @Override
-                    public IkePayload decodeIkePayload(
-                            int payloadType, boolean isCritical, boolean isResp, byte[] payloadBody)
-                            throws IkeProtocolException {
-                        if (support(payloadType)) {
-                            return new TestIkeSupportedPayload(payloadType, isCritical);
-                        } else {
-                            return new IkeUnsupportedPayload(payloadType, isCritical);
-                        }
+        mSpyIkePayloadDecoder = spy(new IkePayloadFactory.IkePayloadDecoder());
+        doAnswer(
+                (invocation) -> {
+                    int payloadType = (int) invocation.getArguments()[0];
+                    boolean isCritical = (boolean) invocation.getArguments()[1];
+                    if (support(payloadType)) {
+                        return new TestIkeSupportedPayload(payloadType, isCritical);
                     }
-                };
+                    return new IkeUnsupportedPayload(payloadType, isCritical);
+                })
+                .when(mSpyIkePayloadDecoder)
+                .decodeIkePayload(anyInt(), anyBoolean(), anyBoolean(), any());
+
+        IkePayloadFactory.sDecoderInstance = mSpyIkePayloadDecoder;
 
         mIkeAuthPacket = TestUtils.hexStringToByteArray(IKE_AUTH_HEX_STRING);
         mUnencryptedPaddedData =
@@ -222,8 +248,9 @@
         when(mMockIkeSaRecord.getInboundDecryptionKey()).thenReturn(new byte[0]);
         when(mMockIkeSaRecord.getInboundIntegrityKey()).thenReturn(new byte[0]);
 
-        mMockFragOneHeader = mock(IkeHeader.class);
-        mMockFragTwoHeader = mock(IkeHeader.class);
+        mIkeFragPacket = TestUtils.hexStringToByteArray(IKE_FRAG_HEX_STRING);
+        mFragOneHeader = new IkeHeader(mIkeFragPacket);
+        mFragTwoHeader = new IkeHeader(mIkeFragPacket);
 
         mDummySkfPayloadOne =
                 makeDummySkfPayload(
@@ -349,7 +376,8 @@
                         mMockCipher,
                         mMockIkeSaRecord,
                         mIkeAuthHeader,
-                        mIkeAuthPacket);
+                        mIkeAuthPacket,
+                        null /*collectedFragments*/);
         IkeMessage ikeMessage = verifyDecodeResultOkAndGetMessage(decodeResult);
 
         assertEquals(IKE_AUTH_PAYLOAD_SIZE, ikeMessage.ikePayloadList.size());
@@ -364,7 +392,8 @@
                         mMockCipher,
                         mMockIkeSaRecord,
                         mIkeAuthHeader,
-                        mIkeAuthPacket);
+                        mIkeAuthPacket,
+                        null /*collectedFragments*/);
         IkeException ikeException =
                 verifyDecodeResultErrorAndGetIkeException(
                         decodeResult, DECODE_STATUS_UNPROTECTED_ERROR);
@@ -383,7 +412,8 @@
                         mMockCipher,
                         mMockIkeSaRecord,
                         mIkeAuthHeader,
-                        mIkeAuthPacket);
+                        mIkeAuthPacket,
+                        null /*collectedFragments*/);
         IkeException ikeException =
                 verifyDecodeResultErrorAndGetIkeException(
                         decodeResult, DECODE_STATUS_UNPROTECTED_ERROR);
@@ -404,7 +434,9 @@
                         mMockCipher,
                         mMockIkeSaRecord,
                         mIkeAuthHeader,
-                        mIkeAuthPacket);
+                        mIkeAuthPacket,
+                        null /*collectedFragments*/);
+
         IkeException ikeException =
                 verifyDecodeResultErrorAndGetIkeException(
                         decodeResult, DECODE_STATUS_UNPROTECTED_ERROR);
@@ -429,7 +461,8 @@
                         mMockCipher,
                         mMockIkeSaRecord,
                         mIkeAuthHeader,
-                        mIkeAuthPacket);
+                        mIkeAuthPacket,
+                        null /*collectedFragments*/);
         IkeException ikeException =
                 verifyDecodeResultErrorAndGetIkeException(
                         decodeResult, DECODE_STATUS_PROTECTED_ERROR);
@@ -487,19 +520,20 @@
 
     private DecodeResultPartial makeDecodeResultForFragOne(DecodeResultPartial collectedFrags) {
         return new DecodeResultPartial(
-                mMockFragOneHeader, mDummySkfPayloadOne, PAYLOAD_TYPE_AUTH, collectedFrags);
+                mFragOneHeader, mDummySkfPayloadOne, PAYLOAD_TYPE_AUTH, collectedFrags);
     }
 
     private DecodeResultPartial makeDecodeResultForFragTwo(DecodeResultPartial collectedFrags) {
         return new DecodeResultPartial(
-                mMockFragTwoHeader, mDummySkfPayloadTwo, PAYLOAD_TYPE_NO_NEXT, collectedFrags);
+                mFragTwoHeader, mDummySkfPayloadTwo, PAYLOAD_TYPE_NO_NEXT, collectedFrags);
     }
 
     @Test
     public void testConstructDecodePartialFirstFragArriveFirst() throws Exception {
         DecodeResultPartial resultPartial = makeDecodeResultForFragOne(null /*collectedFragments*/);
 
-        assertEquals(mMockFragOneHeader, resultPartial.ikeHeader);
+        assertEquals(PAYLOAD_TYPE_AUTH, resultPartial.firstPayloadType);
+        assertEquals(mFragOneHeader, resultPartial.ikeHeader);
 
         assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length);
         assertArrayEquals(
@@ -513,7 +547,7 @@
         DecodeResultPartial resultPartial = makeDecodeResultForFragTwo(null /*collectedFragments*/);
 
         assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType);
-        assertEquals(mMockFragTwoHeader, resultPartial.ikeHeader);
+        assertEquals(mFragTwoHeader, resultPartial.ikeHeader);
 
         assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length);
         assertArrayEquals(
@@ -530,7 +564,7 @@
                 makeDecodeResultForFragOne(resultPartialIncomplete);
 
         assertEquals(PAYLOAD_TYPE_AUTH, resultPartialComplete.firstPayloadType);
-        assertEquals(mMockFragTwoHeader, resultPartialComplete.ikeHeader);
+        assertEquals(mFragTwoHeader, resultPartialComplete.ikeHeader);
 
         assertEquals(TOTAL_FRAGMENTS, resultPartialComplete.collectedFragsList.length);
         assertTrue(resultPartialComplete.isAllFragmentsReceived());
@@ -544,11 +578,12 @@
                 makeDecodeResultForFragTwo(resultPartialIncomplete);
 
         assertEquals(PAYLOAD_TYPE_AUTH, resultPartialIncomplete.firstPayloadType);
-        assertEquals(mMockFragOneHeader, resultPartialIncomplete.ikeHeader);
+        assertEquals(mFragOneHeader, resultPartialIncomplete.ikeHeader);
 
         assertEquals(TOTAL_FRAGMENTS, resultPartialIncomplete.collectedFragsList.length);
         assertTrue(resultPartialIncomplete.isAllFragmentsReceived());
 
+
         // Verify reassembly result
         ByteBuffer expectedBuffer =
                 ByteBuffer.allocate(
@@ -573,4 +608,217 @@
 
         }
     }
+
+    private void setDecryptSkfPayload(IkeSkfPayload skf) throws Exception {
+        doReturn(skf)
+                .when(mSpyIkePayloadDecoder)
+                .decodeIkeSkPayload(
+                        eq(true),
+                        anyBoolean(),
+                        any(),
+                        eq(mMockIntegrity),
+                        eq(mMockCipher),
+                        any(),
+                        any());
+    }
+
+    private DecodeResult decodeSkf(
+            int expectedMsgId,
+            IkeHeader header,
+            byte[] packet,
+            DecodeResultPartial collectFragments)
+            throws Exception {
+        return IkeMessage.decode(
+                expectedMsgId,
+                mMockIntegrity,
+                mMockCipher,
+                mMockIkeSaRecord,
+                header,
+                packet,
+                collectFragments);
+    }
+
+    @Test
+    public void testRcvFirstArrivedFrag() throws Exception {
+        setDecryptSkfPayload(mDummySkfPayloadTwo);
+        DecodeResult decodeResult =
+                decodeSkf(
+                        IKE_FRAG_EXPECTED_MESSAGE_ID,
+                        mFragTwoHeader,
+                        mIkeFragPacket,
+                        null /* collectedFragments*/);
+
+        // Verify decoding result
+        assertTrue(decodeResult instanceof DecodeResultPartial);
+        DecodeResultPartial resultPartial = (DecodeResultPartial) decodeResult;
+
+        assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType);
+        assertEquals(mFragTwoHeader, resultPartial.ikeHeader);
+
+        assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length);
+        assertArrayEquals(
+                FRAGMENT_TWO_UNENCRYPTED_DATA,
+                resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]);
+        assertFalse(resultPartial.isAllFragmentsReceived());
+    }
+
+    @Test
+    public void testRcvLastArrivedFrag() throws Exception {
+        // Create two dummy SKF Payloads so that the complete unencrypted data is two ID payloads
+        byte[] idInitPayloadBytes = TestUtils.hexStringToByteArray(ID_INIT_PAYLOAD_HEX_STRING);
+        byte[] idRespPayloadBytes = TestUtils.hexStringToByteArray(ID_RESP_PAYLOAD_HEX_STRING);
+        IkeSkfPayload skfOne =
+                makeDummySkfPayload(idInitPayloadBytes, FRAGMENT_NUM_ONE, TOTAL_FRAGMENTS);
+        IkeSkfPayload skfTwo =
+                makeDummySkfPayload(idRespPayloadBytes, FRAGMENT_NUM_TWO, TOTAL_FRAGMENTS);
+
+        DecodeResultPartial resultPartialIncomplete =
+                new DecodeResultPartial(
+                        mFragOneHeader,
+                        skfOne,
+                        PAYLOAD_TYPE_ID_INITIATOR,
+                        null /* collectedFragments*/);
+
+        setDecryptSkfPayload(skfTwo);
+        DecodeResult decodeResult =
+                decodeSkf(
+                        IKE_FRAG_EXPECTED_MESSAGE_ID,
+                        mFragTwoHeader,
+                        mIkeFragPacket,
+                        resultPartialIncomplete);
+
+        // Verify fragments reassembly has been finished and complete message has been decoded.
+        assertTrue(decodeResult instanceof DecodeResultOk);
+        DecodeResultOk resultOk = (DecodeResultOk) decodeResult;
+        assertEquals(2, resultOk.ikeMessage.ikePayloadList.size());
+    }
+
+    @Test
+    public void testRcvFirstArrivedFragWithUnprotectedError() throws Exception {
+        DecodeResult decodeResult =
+                decodeSkf(
+                        IKE_FRAG_EXPECTED_MESSAGE_ID + 1,
+                        mFragTwoHeader,
+                        mIkeFragPacket,
+                        null /* collectedFragments*/);
+
+        // Verify that unprotected error was returned
+        IkeException ikeException =
+                verifyDecodeResultErrorAndGetIkeException(
+                        decodeResult, DECODE_STATUS_UNPROTECTED_ERROR);
+        assertTrue(ikeException instanceof InvalidMessageIdException);
+    }
+
+    @Test
+    public void testRcvLastArrivedFragWithUnprotectedError() throws Exception {
+        DecodeResultPartial resultPartialIncomplete =
+                makeDecodeResultForFragOne(null /* collectedFragments*/);
+
+        DecodeResult decodeResult =
+                decodeSkf(
+                        IKE_FRAG_EXPECTED_MESSAGE_ID + 1,
+                        mFragTwoHeader,
+                        mIkeFragPacket,
+                        resultPartialIncomplete);
+
+        // Verify that newly received fragment was discarded
+        assertEquals(resultPartialIncomplete, decodeResult);
+    }
+
+    @Test
+    public void testRcvFragWithLargerTotalFragments() throws Exception {
+        DecodeResultPartial resultPartialIncomplete =
+                new DecodeResultPartial(
+                        mFragOneHeader,
+                        mDummySkfPayloadOne,
+                        PAYLOAD_TYPE_NO_NEXT,
+                        null /* collectedFragments*/);
+
+        // Set total fragments of inbound fragment to 5
+        int totalFragments = 5;
+        byte[] fragPacket = TestUtils.hexStringToByteArray(IKE_FRAG_HEX_STRING);
+        fragPacket[TOTAL_FRAGMENTS_OFFSET] = 0;
+        fragPacket[TOTAL_FRAGMENTS_OFFSET] = 5;
+
+        byte[] unencryptedData = "testRcvFragWithLargerTotalFragments".getBytes();
+        IkeSkfPayload skfPayload =
+                makeDummySkfPayload(unencryptedData, FRAGMENT_NUM_TWO, totalFragments);
+        setDecryptSkfPayload(skfPayload);
+        DecodeResult decodeResult =
+                decodeSkf(
+                        IKE_FRAG_EXPECTED_MESSAGE_ID,
+                        mFragTwoHeader,
+                        fragPacket,
+                        resultPartialIncomplete);
+
+        // Verify that previously collected fragments were all discarded
+        assertTrue(decodeResult instanceof DecodeResultPartial);
+        DecodeResultPartial resultPartial = (DecodeResultPartial) decodeResult;
+
+        assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType);
+        assertEquals(mFragTwoHeader, resultPartial.ikeHeader);
+
+        assertEquals(totalFragments, resultPartial.collectedFragsList.length);
+        assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]);
+        assertFalse(resultPartial.isAllFragmentsReceived());
+    }
+
+    @Test
+    public void testRcvFragWithSmallerTotalFragments() throws Exception {
+        int totalFragments = 5;
+        byte[] unencryptedData = "testRcvFragWithSmallerTotalFragments".getBytes();
+        IkeSkfPayload skfPayload =
+                makeDummySkfPayload(unencryptedData, FRAGMENT_NUM_ONE, totalFragments);
+
+        DecodeResultPartial resultPartialIncomplete =
+                new DecodeResultPartial(
+                        mFragOneHeader,
+                        skfPayload,
+                        PAYLOAD_TYPE_AUTH,
+                        null /* collectedFragments*/);
+
+        setDecryptSkfPayload(mDummySkfPayloadTwo);
+        DecodeResult decodeResult =
+                decodeSkf(
+                        IKE_FRAG_EXPECTED_MESSAGE_ID,
+                        mFragTwoHeader,
+                        mIkeFragPacket,
+                        resultPartialIncomplete);
+
+        // Verify that newly received fragment was discarded
+        assertEquals(resultPartialIncomplete, decodeResult);
+    }
+
+    @Test
+    public void testRcvReplayFrag() throws Exception {
+        DecodeResultPartial resultPartialIncomplete =
+                makeDecodeResultForFragTwo(null /* collectedFragments*/);
+
+        setDecryptSkfPayload(mDummySkfPayloadTwo);
+        DecodeResult decodeResult =
+                decodeSkf(
+                        IKE_FRAG_EXPECTED_MESSAGE_ID,
+                        mFragTwoHeader,
+                        mIkeFragPacket,
+                        resultPartialIncomplete);
+
+        // Verify that newly received fragment was discarded
+        assertEquals(resultPartialIncomplete, decodeResult);
+    }
+
+    @Test
+    public void testRcvCompleteMessageDuringReassembly() throws Exception {
+        DecodeResultPartial resultPartialIncomplete =
+                makeDecodeResultForFragTwo(null /* collectedFragments*/);
+
+        DecodeResult decodeResult =
+                decodeSkf(
+                        IKE_AUTH_EXPECTED_MESSAGE_ID,
+                        mIkeAuthHeader,
+                        mIkeAuthPacket,
+                        resultPartialIncomplete);
+
+        // Verify that newly received IKE message was discarded
+        assertEquals(resultPartialIncomplete, decodeResult);
+    }
 }
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java
index 26f3d01..f3af5b6 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java
@@ -63,7 +63,7 @@
     private static final int CHECKSUM_LEN = 12;
 
     private IkeCipher mAesCbcDecryptCipher;
-    private byte[] mAesCbcDecryptKey;
+    private byte[] mAesCbcDecryptionKey;
 
     private IkeMacIntegrity mHmacSha1IntegrityMac;
     private byte[] mHmacSha1IntegrityKey;
@@ -76,7 +76,7 @@
                                 SaProposal.ENCRYPTION_ALGORITHM_AES_CBC,
                                 SaProposal.KEY_LEN_AES_128),
                         IkeMessage.getSecurityProvider());
-        mAesCbcDecryptKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP);
+        mAesCbcDecryptionKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP);
         mHmacSha1IntegrityMac =
                 IkeMacIntegrity.create(
                         new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96),
@@ -97,7 +97,7 @@
                                 mHmacSha1IntegrityMac,
                                 mAesCbcDecryptCipher,
                                 mHmacSha1IntegrityKey,
-                                mAesCbcDecryptKey)
+                                mAesCbcDecryptionKey)
                         .first;
         int payloadLength = payload.getPayloadLength();
         ByteBuffer buffer = ByteBuffer.allocate(payloadLength);
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkfPayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkfPayloadTest.java
index e4c25d3..20c464b 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkfPayloadTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkfPayloadTest.java
@@ -111,7 +111,7 @@
                                         mSpyHmacSha256IntegrityMac,
                                         mSpyAesCbcCipher,
                                         new byte[0] /*integrityKey*/,
-                                        new byte[0] /*decryptKey*/)
+                                        new byte[0] /*decryptionKey*/)
                                 .first;
         return payload;
     }