changeset 15967:42268eb6e04e

8167680: DTLS implementation bugs Reviewed-by: jnimeh, asmotrak
author xuelei
date Sat, 29 Oct 2016 13:34:53 +0000
parents 3192d7aa428d
children 00192a14fdc6
files src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java src/java.base/share/classes/sun/security/ssl/DTLSRecord.java src/java.base/share/classes/sun/security/ssl/Debug.java src/java.base/share/classes/sun/security/ssl/OutputRecord.java src/java.base/share/classes/sun/security/ssl/Plaintext.java src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java test/javax/net/ssl/DTLS/DTLSOverDatagram.java test/javax/net/ssl/DTLS/PacketLossRetransmission.java test/javax/net/ssl/DTLS/RespondToRetransmit.java test/javax/net/ssl/TLSCommon/SSLEngineTestCase.java
diffstat 12 files changed, 1372 insertions(+), 589 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java	Fri Oct 28 10:10:06 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java	Sat Oct 29 13:34:53 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -42,10 +42,6 @@
 
     private DTLSReassembler reassembler = null;
 
-    // Cache the session identifier for the detection of session-resuming
-    // handshake.
-    byte[]              prevSessionID = new byte[0];
-
     int                 readEpoch;
 
     int                 prevReadEpoch;
@@ -114,13 +110,7 @@
     @Override
     Plaintext acquirePlaintext() {
         if (reassembler != null) {
-            Plaintext plaintext = reassembler.acquirePlaintext();
-            if (reassembler.finished()) {
-                // discard all buffered unused message.
-                reassembler = null;
-            }
-
-            return plaintext;
+            return reassembler.acquirePlaintext();
         }
 
         return null;
@@ -149,40 +139,54 @@
         packet.get(recordEnS);
         int recordEpoch = ((recordEnS[0] & 0xFF) << 8) |
                            (recordEnS[1] & 0xFF);          // pos: 3, 4
-        long recordSeq  = Authenticator.toLong(recordEnS);
+        long recordSeq  = ((recordEnS[2] & 0xFFL) << 40) |
+                          ((recordEnS[3] & 0xFFL) << 32) |
+                          ((recordEnS[4] & 0xFFL) << 24) |
+                          ((recordEnS[5] & 0xFFL) << 16) |
+                          ((recordEnS[6] & 0xFFL) <<  8) |
+                           (recordEnS[7] & 0xFFL);         // pos: 5-10
+
         int contentLen = ((packet.get() & 0xFF) << 8) |
-                          (packet.get() & 0xFF);            // pos: 11, 12
+                          (packet.get() & 0xFF);           // pos: 11, 12
 
         if (debug != null && Debug.isOn("record")) {
-             System.out.println(Thread.currentThread().getName() +
-                    ", READ: " +
+            Debug.log("READ: " +
                     ProtocolVersion.valueOf(majorVersion, minorVersion) +
                     " " + Record.contentName(contentType) + ", length = " +
                     contentLen);
         }
 
         int recLim = srcPos + DTLSRecord.headerSize + contentLen;
-        if (this.readEpoch > recordEpoch) {
-            // Discard old records delivered before this epoch.
 
+        if (this.prevReadEpoch > recordEpoch) {
             // Reset the position of the packet buffer.
             packet.position(recLim);
+            if (debug != null && Debug.isOn("record")) {
+                Debug.printHex("READ: discard this old record", recordEnS);
+            }
             return null;
         }
 
+        // Buffer next epoch message if necessary.
         if (this.readEpoch < recordEpoch) {
-            if (contentType != Record.ct_handshake) {
-                // just discard it if not a handshake message
+            // Discard the record younger than the current epcoh if:
+            // 1. it is not a handshake message, or
+            // 2. it is not of next epoch.
+            if (((contentType != Record.ct_handshake) &&
+                    (contentType != Record.ct_change_cipher_spec)) ||
+                (this.readEpoch < (recordEpoch - 1))) {
+
                 packet.position(recLim);
+
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("Premature record (epoch), discard it.");
+                }
+
                 return null;
             }
 
-            // Not ready to decrypt this record, may be encrypted Finished
+            // Not ready to decrypt this record, may be an encrypted Finished
             // message, need to buffer it.
-            if (reassembler == null) {
-               reassembler = new DTLSReassembler();
-            }
-
             byte[] fragment = new byte[contentLen];
             packet.get(fragment);              // copy the fragment
             RecordFragment buffered = new RecordFragment(fragment, contentType,
@@ -194,94 +198,130 @@
             // consume the full record in the packet buffer.
             packet.position(recLim);
 
-            Plaintext plaintext = reassembler.acquirePlaintext();
-            if (reassembler.finished()) {
-                // discard all buffered unused message.
+            return reassembler.acquirePlaintext();
+        }
+
+        //
+        // Now, the message is of this epoch or the previous epoch.
+        //
+        Authenticator decodeAuthenticator;
+        CipherBox decodeCipher;
+        if (this.readEpoch == recordEpoch) {
+            decodeAuthenticator = readAuthenticator;
+            decodeCipher = readCipher;
+        } else {                        // prevReadEpoch == recordEpoch
+            decodeAuthenticator = prevReadAuthenticator;
+            decodeCipher = prevReadCipher;
+        }
+
+        // decrypt the fragment
+        packet.limit(recLim);
+        packet.position(srcPos + DTLSRecord.headerSize);
+
+        ByteBuffer plaintextFragment;
+        try {
+            plaintextFragment = decrypt(decodeAuthenticator,
+                    decodeCipher, contentType, packet, recordEnS);
+        } catch (BadPaddingException bpe) {
+            if (debug != null && Debug.isOn("ssl")) {
+                Debug.log("Discard invalid record: " + bpe);
+            }
+
+            // invalid, discard this record [section 4.1.2.7, RFC 6347]
+            return null;
+        } finally {
+            // comsume a complete record
+            packet.limit(srcLim);
+            packet.position(recLim);
+        }
+
+        if (contentType != Record.ct_change_cipher_spec &&
+            contentType != Record.ct_handshake) {   // app data or alert
+                                                    // no retransmission
+            // Cleanup the handshake reassembler if necessary.
+            if ((reassembler != null) &&
+                    (reassembler.handshakeEpoch < recordEpoch)) {
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("Cleanup the handshake reassembler");
+                }
+
                 reassembler = null;
             }
 
-            return plaintext;
+            return new Plaintext(contentType, majorVersion, minorVersion,
+                    recordEpoch, Authenticator.toLong(recordEnS),
+                    plaintextFragment);
         }
 
-        if (this.readEpoch == recordEpoch) {
-            // decrypt the fragment
-            packet.limit(recLim);
-            packet.position(srcPos + DTLSRecord.headerSize);
-
-            ByteBuffer plaintextFragment;
-            try {
-                plaintextFragment = decrypt(readAuthenticator,
-                        readCipher, contentType, packet, recordEnS);
-            } catch (BadPaddingException bpe) {
-                if (debug != null && Debug.isOn("ssl")) {
-                    System.out.println(Thread.currentThread().getName() +
-                            " discard invalid record: " + bpe);
-                }
-
-                // invalid, discard this record [section 4.1.2.7, RFC 6347]
-                return null;
-            } finally {
-                // comsume a complete record
-                packet.limit(srcLim);
-                packet.position(recLim);
-            }
-
-            if (contentType != Record.ct_change_cipher_spec &&
-                contentType != Record.ct_handshake) {   // app data or alert
-                                                        // no retransmission
-               return new Plaintext(contentType, majorVersion, minorVersion,
-                        recordEpoch, recordSeq, plaintextFragment);
-            }
-
-            if (contentType == Record.ct_change_cipher_spec) {
-                if (reassembler == null) {
+        if (contentType == Record.ct_change_cipher_spec) {
+            if (reassembler == null) {
+                if (this.readEpoch != recordEpoch) {
                     // handshake has not started, should be an
                     // old handshake message, discard it.
+
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log(
+                                "Lagging behind ChangeCipherSpec, discard it.");
+                    }
+
                     return null;
                 }
 
-                reassembler.queueUpFragment(
-                        new RecordFragment(plaintextFragment, contentType,
-                                majorVersion, minorVersion,
-                                recordEnS, recordEpoch, recordSeq, false));
-            } else {    // handshake record
-                // One record may contain 1+ more handshake messages.
-                while (plaintextFragment.remaining() > 0) {
+                reassembler = new DTLSReassembler(recordEpoch);
+            }
 
-                    HandshakeFragment hsFrag = parseHandshakeMessage(
-                        contentType, majorVersion, minorVersion,
-                        recordEnS, recordEpoch, recordSeq, plaintextFragment);
+            reassembler.queueUpChangeCipherSpec(
+                    new RecordFragment(plaintextFragment, contentType,
+                            majorVersion, minorVersion,
+                            recordEnS, recordEpoch, recordSeq, false));
+        } else {    // handshake record
+            // One record may contain 1+ more handshake messages.
+            while (plaintextFragment.remaining() > 0) {
 
-                    if (hsFrag == null) {
-                        // invalid, discard this record
+                HandshakeFragment hsFrag = parseHandshakeMessage(
+                    contentType, majorVersion, minorVersion,
+                    recordEnS, recordEpoch, recordSeq, plaintextFragment);
+
+                if (hsFrag == null) {
+                    // invalid, discard this record
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log("Invalid handshake message, discard it.");
+                    }
+
+                    return null;
+                }
+
+                if (reassembler == null) {
+                    if (this.readEpoch != recordEpoch) {
+                        // handshake has not started, should be an
+                        // old handshake message, discard it.
+
+                        if (debug != null && Debug.isOn("verbose")) {
+                            Debug.log(
+                                "Lagging behind handshake record, discard it.");
+                        }
+
                         return null;
                     }
 
-                    if ((reassembler == null) &&
-                            isKickstart(hsFrag.handshakeType)) {
-                       reassembler = new DTLSReassembler();
-                    }
-
-                    if (reassembler != null) {
-                        reassembler.queueUpHandshake(hsFrag);
-                    }   // else, just ignore the message.
-                }
-            }
-
-            // Completed the read of the full record. Acquire the reassembled
-            // messages.
-            if (reassembler != null) {
-                Plaintext plaintext = reassembler.acquirePlaintext();
-                if (reassembler.finished()) {
-                    // discard all buffered unused message.
-                    reassembler = null;
+                    reassembler = new DTLSReassembler(recordEpoch);
                 }
 
-                return plaintext;
+                reassembler.queueUpHandshake(hsFrag);
             }
         }
 
-        return null;    // make the complier happy
+        // Completed the read of the full record.  Acquire the reassembled
+        // messages.
+        if (reassembler != null) {
+            return reassembler.acquirePlaintext();
+        }
+
+        if (debug != null && Debug.isOn("verbose")) {
+            Debug.log("The reassembler is not initialized yet.");
+        }
+
+        return null;
     }
 
     @Override
@@ -330,12 +370,6 @@
         }
     }
 
-    private static boolean isKickstart(byte handshakeType) {
-        return (handshakeType == HandshakeMessage.ht_client_hello) ||
-               (handshakeType == HandshakeMessage.ht_hello_request) ||
-               (handshakeType == HandshakeMessage.ht_hello_verify_request);
-    }
-
     private static HandshakeFragment parseHandshakeMessage(
             byte contentType, byte majorVersion, byte minorVersion,
             byte[] recordEnS, int recordEpoch, long recordSeq,
@@ -344,9 +378,7 @@
         int remaining = plaintextFragment.remaining();
         if (remaining < handshakeHeaderSize) {
             if (debug != null && Debug.isOn("ssl")) {
-                System.out.println(
-                        Thread.currentThread().getName() +
-                        " discard invalid record: " +
+                Debug.log("Discard invalid record: " +
                         "too small record to hold a handshake fragment");
             }
 
@@ -372,9 +404,7 @@
                  (plaintextFragment.get() & 0xFF);          // pos: 9-11
         if ((remaining - handshakeHeaderSize) < fragmentLength) {
             if (debug != null && Debug.isOn("ssl")) {
-                System.out.println(
-                        Thread.currentThread().getName() +
-                        " discard invalid record: " +
+                Debug.log("Discard invalid record: " +
                         "not a complete handshake fragment in the record");
             }
 
@@ -431,7 +461,39 @@
 
         @Override
         public int compareTo(RecordFragment o) {
-            return Long.compareUnsigned(this.recordSeq, o.recordSeq);
+            if (this.contentType == Record.ct_change_cipher_spec) {
+                if (o.contentType == Record.ct_change_cipher_spec) {
+                    // Only one incoming ChangeCipherSpec message for an epoch.
+                    //
+                    // Ignore duplicated ChangeCipherSpec messages.
+                    return Integer.compare(this.recordEpoch, o.recordEpoch);
+                } else if ((this.recordEpoch == o.recordEpoch) &&
+                        (o.contentType == Record.ct_handshake)) {
+                    // ChangeCipherSpec is the latest message of an epoch.
+                    return 1;
+                }
+            } else if (o.contentType == Record.ct_change_cipher_spec) {
+                if ((this.recordEpoch == o.recordEpoch) &&
+                        (this.contentType == Record.ct_handshake)) {
+                    // ChangeCipherSpec is the latest message of an epoch.
+                    return -1;
+                } else {
+                    // different epoch or this is not a handshake message
+                    return compareToSequence(o.recordEpoch, o.recordSeq);
+                }
+            }
+
+            return compareToSequence(o.recordEpoch, o.recordSeq);
+        }
+
+        int compareToSequence(int epoch, long seq) {
+            if (this.recordEpoch > epoch) {
+                return 1;
+            } else if (this.recordEpoch == epoch) {
+                return Long.compare(this.recordSeq, seq);
+            } else {
+                return -1;
+            }
         }
     }
 
@@ -465,12 +527,24 @@
             if (o instanceof HandshakeFragment) {
                 HandshakeFragment other = (HandshakeFragment)o;
                 if (this.messageSeq != other.messageSeq) {
-                    // keep the insertion order for the same message
+                    // keep the insertion order of handshake messages
                     return this.messageSeq - other.messageSeq;
+                } else if (this.fragmentOffset != other.fragmentOffset) {
+                    // small fragment offset was transmitted first
+                    return this.fragmentOffset - other.fragmentOffset;
+                } else if (this.fragmentLength == other.fragmentLength) {
+                    // retransmissions, ignore duplicated messages.
+                    return 0;
                 }
+
+                // Should be repacked for suitable fragment length.
+                //
+                // Note that the acquiring processes will reassemble the
+                // the fragments later.
+                return compareToSequence(o.recordEpoch, o.recordSeq);
             }
 
-            return Long.compareUnsigned(this.recordSeq, o.recordSeq);
+            return super.compareTo(o);
         }
     }
 
@@ -484,24 +558,72 @@
         }
     }
 
+    private static final class HandshakeFlight implements Cloneable {
+        static final byte HF_UNKNOWN = HandshakeMessage.ht_not_applicable;
+
+        byte        handshakeType;      // handshake type
+        int         flightEpoch;        // the epoch of the first message
+        int         minMessageSeq;      // minimal message sequence
+
+        int         maxMessageSeq;      // maximum message sequence
+        int         maxRecordEpoch;     // maximum record sequence number
+        long        maxRecordSeq;       // maximum record sequence number
+
+        HashMap<Byte, List<HoleDescriptor>> holesMap;
+
+        HandshakeFlight() {
+            this.handshakeType = HF_UNKNOWN;
+            this.flightEpoch = 0;
+            this.minMessageSeq = 0;
+
+            this.maxMessageSeq = 0;
+            this.maxRecordEpoch = 0;
+            this.maxRecordSeq = -1;
+
+            this.holesMap = new HashMap<>(5);
+        }
+
+        boolean isRetransmitOf(HandshakeFlight hs) {
+            return (hs != null) &&
+                   (this.handshakeType == hs.handshakeType) &&
+                   (this.minMessageSeq == hs.minMessageSeq);
+        }
+
+        @Override
+        public Object clone() {
+            HandshakeFlight hf = new HandshakeFlight();
+
+            hf.handshakeType = this.handshakeType;
+            hf.flightEpoch = this.flightEpoch;
+            hf.minMessageSeq = this.minMessageSeq;
+
+            hf.maxMessageSeq = this.maxMessageSeq;
+            hf.maxRecordEpoch = this.maxRecordEpoch;
+            hf.maxRecordSeq = this.maxRecordSeq;
+
+            hf.holesMap = new HashMap<>(this.holesMap);
+
+            return hf;
+        }
+    }
+
     final class DTLSReassembler {
+        // The handshake epoch.
+        final int handshakeEpoch;
+
+        // The buffered fragments.
         TreeSet<RecordFragment> bufferedFragments = new TreeSet<>();
 
-        HashMap<Byte, List<HoleDescriptor>> holesMap = new HashMap<>(5);
+        // The handshake flight in progress.
+        HandshakeFlight handshakeFlight = new HandshakeFlight();
 
-        // Epoch, sequence number and handshake message sequence of the
-        // beginning message of a flight.
-        byte        flightType = (byte)0xFF;
-
-        int         flightTopEpoch = 0;
-        long        flightTopRecordSeq = -1;
-        int         flightTopMessageSeq = 0;
+        // The preceding handshake flight.
+        HandshakeFlight precedingFlight = null;
 
         // Epoch, sequence number and handshake message sequence of the
         // next message acquisition of a flight.
-        int         nextRecordEpoch = 0;    // next record epoch
+        int         nextRecordEpoch;        // next record epoch
         long        nextRecordSeq = 0;      // next record sequence number
-        int         nextMessageSeq = 0;     // next handshake message number
 
         // Expect ChangeCipherSpec and Finished messages for the final flight.
         boolean     expectCCSFlight = false;
@@ -510,65 +632,66 @@
         boolean     flightIsReady = false;
         boolean     needToCheckFlight = false;
 
-        // Is it a session-resuming abbreviated handshake.?
-        boolean     isAbbreviatedHandshake = false;
+        DTLSReassembler(int handshakeEpoch) {
+            this.handshakeEpoch = handshakeEpoch;
+            this.nextRecordEpoch = handshakeEpoch;
 
-        // The handshke fragment with the biggest record sequence number
-        // in a flight, not counting the Finished message.
-        HandshakeFragment lastHandshakeFragment = null;
-
-        // Is handshake (intput) finished?
-        boolean handshakeFinished = false;
-
-        DTLSReassembler() {
-            // blank
-        }
-
-        boolean finished() {
-            return handshakeFinished;
+            this.handshakeFlight.flightEpoch = handshakeEpoch;
         }
 
         void expectingFinishFlight() {
             expectCCSFlight = true;
         }
 
+        // Queue up a handshake message.
         void queueUpHandshake(HandshakeFragment hsf) {
-
-            if ((nextRecordEpoch > hsf.recordEpoch) ||
-                    (nextRecordSeq > hsf.recordSeq) ||
-                    (nextMessageSeq > hsf.messageSeq)) {
-                // too old, discard this record
+            if (!isDesirable(hsf)) {
+                // Not a dedired record, discard it.
                 return;
             }
 
+            // Clean up the retransmission messages if necessary.
+            cleanUpRetransmit(hsf);
+
             // Is it the first message of next flight?
-            if ((flightTopMessageSeq == hsf.messageSeq) &&
-                    (hsf.fragmentOffset == 0) && (flightTopRecordSeq == -1)) {
+            //
+            // Note: the Finished message is handled in the final CCS flight.
+            boolean isMinimalFlightMessage = false;
+            if (handshakeFlight.minMessageSeq == hsf.messageSeq) {
+                isMinimalFlightMessage = true;
+            } else if ((precedingFlight != null) &&
+                    (precedingFlight.minMessageSeq == hsf.messageSeq)) {
+                isMinimalFlightMessage = true;
+            }
 
-                flightType = hsf.handshakeType;
-                flightTopEpoch = hsf.recordEpoch;
-                flightTopRecordSeq = hsf.recordSeq;
+            if (isMinimalFlightMessage && (hsf.fragmentOffset == 0) &&
+                    (hsf.handshakeType != HandshakeMessage.ht_finished)) {
 
-                if (hsf.handshakeType == HandshakeMessage.ht_server_hello) {
-                    // Is it a session-resuming handshake?
-                    try {
-                        isAbbreviatedHandshake =
-                                isSessionResuming(hsf.fragment, prevSessionID);
-                    } catch (SSLException ssle) {
-                        if (debug != null && Debug.isOn("ssl")) {
-                            System.out.println(
-                                    Thread.currentThread().getName() +
-                                    " discard invalid record: " + ssle);
-                        }
+                // reset the handshake flight
+                handshakeFlight.handshakeType = hsf.handshakeType;
+                handshakeFlight.flightEpoch = hsf.recordEpoch;
+                handshakeFlight.minMessageSeq = hsf.messageSeq;
+            }
 
-                        // invalid, discard it [section 4.1.2.7, RFC 6347]
-                        return;
+            if (hsf.handshakeType == HandshakeMessage.ht_finished) {
+                handshakeFlight.maxMessageSeq = hsf.messageSeq;
+                handshakeFlight.maxRecordEpoch = hsf.recordEpoch;
+                handshakeFlight.maxRecordSeq = hsf.recordSeq;
+            } else {
+                if (handshakeFlight.maxMessageSeq < hsf.messageSeq) {
+                    handshakeFlight.maxMessageSeq = hsf.messageSeq;
+                }
+
+                int n = (hsf.recordEpoch - handshakeFlight.maxRecordEpoch);
+                if (n > 0) {
+                    handshakeFlight.maxRecordEpoch = hsf.recordEpoch;
+                    handshakeFlight.maxRecordSeq = hsf.recordSeq;
+                } else if (n == 0) {
+                    // the same epoch
+                    if (handshakeFlight.maxRecordSeq < hsf.recordSeq) {
+                        handshakeFlight.maxRecordSeq = hsf.recordSeq;
                     }
-
-                    if (!isAbbreviatedHandshake) {
-                        prevSessionID = getSessionID(hsf.fragment);
-                    }
-                }
+                }   // Otherwise, it is unlikely to happen.
             }
 
             boolean fragmented = false;
@@ -578,7 +701,8 @@
                 fragmented = true;
             }
 
-            List<HoleDescriptor> holes = holesMap.get(hsf.handshakeType);
+            List<HoleDescriptor> holes =
+                    handshakeFlight.holesMap.get(hsf.handshakeType);
             if (holes == null) {
                 if (!fragmented) {
                     holes = Collections.emptyList();
@@ -586,7 +710,7 @@
                     holes = new LinkedList<HoleDescriptor>();
                     holes.add(new HoleDescriptor(0, hsf.messageLength));
                 }
-                holesMap.put(hsf.handshakeType, holes);
+                handshakeFlight.holesMap.put(hsf.handshakeType, holes);
             } else if (holes.isEmpty()) {
                 // Have got the full handshake message.  This record may be
                 // a handshake message retransmission.  Discard this record.
@@ -594,20 +718,11 @@
                 // It's OK to discard retransmission as the handshake hash
                 // is computed as if each handshake message had been sent
                 // as a single fragment.
-                //
-                // Note that ClientHello messages are delivered twice in
-                // DTLS handshaking.
-                if ((hsf.handshakeType != HandshakeMessage.ht_client_hello &&
-                     hsf.handshakeType != ht_hello_verify_request) ||
-                        (nextMessageSeq != hsf.messageSeq)) {
-                    return;
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("Have got the full message, discard it.");
                 }
 
-                if (fragmented) {
-                    holes = new LinkedList<HoleDescriptor>();
-                    holes.add(new HoleDescriptor(0, hsf.messageLength));
-                }
-                holesMap.put(hsf.handshakeType, holes);
+                return;
             }
 
             if (fragmented) {
@@ -628,9 +743,7 @@
                          (hole.limit < fragmentLimit))) {
 
                         if (debug != null && Debug.isOn("ssl")) {
-                            System.out.println(
-                                Thread.currentThread().getName() +
-                                " discard invalid record: " +
+                            Debug.log("Discard invalid record: " +
                                 "handshake fragment ranges are overlapping");
                         }
 
@@ -659,48 +772,205 @@
                 }
             }
 
-            // append this fragment
-            bufferedFragments.add(hsf);
-
-            if ((lastHandshakeFragment == null) ||
-                (lastHandshakeFragment.compareTo(hsf) < 0)) {
-
-                lastHandshakeFragment = hsf;
+            // buffer this fragment
+            if (hsf.handshakeType == HandshakeMessage.ht_finished) {
+                // Need no status update.
+                bufferedFragments.add(hsf);
+            } else {
+                bufferFragment(hsf);
             }
-
-            if (flightIsReady) {
-                flightIsReady = false;
-            }
-            needToCheckFlight = true;
         }
 
-        // queue up change_cipher_spec or encrypted message
-        void queueUpFragment(RecordFragment rf) {
-            if ((nextRecordEpoch > rf.recordEpoch) ||
-                    (nextRecordSeq > rf.recordSeq)) {
-                // too old, discard this record
+        // Queue up a ChangeCipherSpec message
+        void queueUpChangeCipherSpec(RecordFragment rf) {
+            if (!isDesirable(rf)) {
+                // Not a dedired record, discard it.
                 return;
             }
 
-            // Is it the first message of next flight?
-            if (expectCCSFlight &&
-                    (rf.contentType == Record.ct_change_cipher_spec)) {
+            // Clean up the retransmission messages if necessary.
+            cleanUpRetransmit(rf);
 
-                flightType = (byte)0xFE;
-                flightTopEpoch = rf.recordEpoch;
-                flightTopRecordSeq = rf.recordSeq;
+            // Is it the first message of this flight?
+            //
+            // Note: the first message of the final flight is ChangeCipherSpec.
+            if (expectCCSFlight) {
+                handshakeFlight.handshakeType = HandshakeFlight.HF_UNKNOWN;
+                handshakeFlight.flightEpoch = rf.recordEpoch;
             }
 
+            // The epoch should be the same as the first message of the flight.
+            if (handshakeFlight.maxRecordSeq < rf.recordSeq) {
+                handshakeFlight.maxRecordSeq = rf.recordSeq;
+            }
+
+            // buffer this fragment
+            bufferFragment(rf);
+        }
+
+        // Queue up a ciphertext message.
+        //
+        // Note: not yet be able to decrypt the message.
+        void queueUpFragment(RecordFragment rf) {
+            if (!isDesirable(rf)) {
+                // Not a dedired record, discard it.
+                return;
+            }
+
+            // Clean up the retransmission messages if necessary.
+            cleanUpRetransmit(rf);
+
+            // buffer this fragment
+            bufferFragment(rf);
+        }
+
+        private void bufferFragment(RecordFragment rf) {
             // append this fragment
             bufferedFragments.add(rf);
 
             if (flightIsReady) {
                 flightIsReady = false;
             }
-            needToCheckFlight = true;
+
+            if (!needToCheckFlight) {
+                needToCheckFlight = true;
+            }
         }
 
-        boolean isEmpty() {
+        private void cleanUpRetransmit(RecordFragment rf) {
+            // Does the next flight start?
+            boolean isNewFlight = false;
+            if (precedingFlight != null) {
+                if (precedingFlight.flightEpoch < rf.recordEpoch) {
+                    isNewFlight = true;
+                } else {
+                    if (rf instanceof HandshakeFragment) {
+                        HandshakeFragment hsf = (HandshakeFragment)rf;
+                        if (precedingFlight.maxMessageSeq  < hsf.messageSeq) {
+                            isNewFlight = true;
+                        }
+                    } else if (rf.contentType != Record.ct_change_cipher_spec) {
+                        // ciphertext
+                        if (precedingFlight.maxRecordEpoch < rf.recordEpoch) {
+                            isNewFlight = true;
+                        }
+                    }
+                }
+            }
+
+            if (!isNewFlight) {
+                // Need no cleanup.
+                return;
+            }
+
+            // clean up the buffer
+            for (Iterator<RecordFragment> it = bufferedFragments.iterator();
+                    it.hasNext();) {
+
+                RecordFragment frag = it.next();
+                boolean isOld = false;
+                if (frag.recordEpoch < precedingFlight.maxRecordEpoch) {
+                    isOld = true;
+                } else if (frag.recordEpoch == precedingFlight.maxRecordEpoch) {
+                    if (frag.recordSeq <= precedingFlight.maxRecordSeq) {
+                        isOld = true;
+                    }
+                }
+
+                if (!isOld && (frag instanceof HandshakeFragment)) {
+                    HandshakeFragment hsf = (HandshakeFragment)frag;
+                    isOld = (hsf.messageSeq <= precedingFlight.maxMessageSeq);
+                }
+
+                if (isOld) {
+                    it.remove();
+                } else {
+                    // Safe to break as items in the buffer are ordered.
+                    break;
+                }
+            }
+
+            // discard retransmissions of the previous flight if any.
+            precedingFlight = null;
+        }
+
+        // Is a desired record?
+        //
+        // Check for retransmission and lost records.
+        private boolean isDesirable(RecordFragment rf) {
+            //
+            // Discard records old than the previous epoch.
+            //
+            int previousEpoch = nextRecordEpoch - 1;
+            if (rf.recordEpoch < previousEpoch) {
+                // Too old to use, discard this record.
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("Too old epoch to use this record, discard it.");
+                }
+
+                return false;
+            }
+
+            //
+            // Allow retransmission of last flight of the previous epoch
+            //
+            // For example, the last server delivered flight for session
+            // resuming abbreviated handshaking consist three messages:
+            //      ServerHello
+            //      [ChangeCipherSpec]
+            //      Finished
+            //
+            // The epoch number is incremented and the sequence number is reset
+            // if the ChangeCipherSpec is sent.
+            if (rf.recordEpoch == previousEpoch) {
+                boolean isDesired = true;
+                if (precedingFlight == null) {
+                    isDesired = false;
+                } else {
+                    if (rf instanceof HandshakeFragment) {
+                        HandshakeFragment hsf = (HandshakeFragment)rf;
+                        if (precedingFlight.minMessageSeq > hsf.messageSeq) {
+                            isDesired = false;
+                        }
+                    } else if (rf.contentType == Record.ct_change_cipher_spec) {
+                        // ChangeCipherSpec
+                        if (precedingFlight.flightEpoch != rf.recordEpoch) {
+                            isDesired = false;
+                        }
+                    } else {        // ciphertext
+                        if ((rf.recordEpoch < precedingFlight.maxRecordEpoch) ||
+                            (rf.recordEpoch == precedingFlight.maxRecordEpoch &&
+                                rf.recordSeq <= precedingFlight.maxRecordSeq)) {
+                            isDesired = false;
+                        }
+                    }
+                }
+
+                if (!isDesired) {
+                    // Too old to use, discard this retransmitted record
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log("Too old retransmission to use, discard it.");
+                    }
+
+                    return false;
+                }
+            } else if ((rf.recordEpoch == nextRecordEpoch) &&
+                    (nextRecordSeq > rf.recordSeq)) {
+
+                // Previously disordered record for the current epoch.
+                //
+                // Should has been retransmitted. Discard this record.
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("Lagging behind record (sequence), discard it.");
+                }
+
+                return false;
+            }
+
+            return true;
+        }
+
+        private boolean isEmpty() {
             return (bufferedFragments.isEmpty() ||
                     (!flightIsReady && !needToCheckFlight) ||
                     (needToCheckFlight && !flightIsReady()));
@@ -708,12 +978,9 @@
 
         Plaintext acquirePlaintext() {
             if (bufferedFragments.isEmpty()) {
-                // reset the flight
-                if (flightIsReady) {
-                    flightIsReady = false;
-                    needToCheckFlight = false;
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("No received handshake messages");
                 }
-
                 return null;
             }
 
@@ -721,27 +988,103 @@
                 // check the fligth status
                 flightIsReady = flightIsReady();
 
-                // set for next flight
+                // Reset if this flight is ready.
                 if (flightIsReady) {
-                    flightTopMessageSeq = lastHandshakeFragment.messageSeq + 1;
-                    flightTopRecordSeq = -1;
+                    // Retransmitted handshake messages are not needed for
+                    // further handshaking processing.
+                    if (handshakeFlight.isRetransmitOf(precedingFlight)) {
+                        // cleanup
+                        bufferedFragments.clear();
+
+                        // Reset the next handshake flight.
+                        resetHandshakeFlight(precedingFlight);
+
+                        if (debug != null && Debug.isOn("verbose")) {
+                            Debug.log("Received a retransmission flight.");
+                        }
+
+                        return Plaintext.PLAINTEXT_NULL;
+                    }
                 }
 
                 needToCheckFlight = false;
             }
 
             if (!flightIsReady) {
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("The handshake flight is not ready to use: " +
+                                handshakeFlight.handshakeType);
+                }
                 return null;
             }
 
             RecordFragment rFrag = bufferedFragments.first();
+            Plaintext plaintext;
             if (!rFrag.isCiphertext) {
                 // handshake message, or ChangeCipherSpec message
-                return acquireHandshakeMessage();
+                plaintext = acquireHandshakeMessage();
+
+                // Reset the handshake flight.
+                if (bufferedFragments.isEmpty()) {
+                    // Need not to backup the holes map.  Clear up it at first.
+                    handshakeFlight.holesMap.clear();   // cleanup holes map
+
+                    // Update the preceding flight.
+                    precedingFlight = (HandshakeFlight)handshakeFlight.clone();
+
+                    // Reset the next handshake flight.
+                    resetHandshakeFlight(precedingFlight);
+
+                    if (expectCCSFlight &&
+                            (precedingFlight.flightEpoch ==
+                                    HandshakeFlight.HF_UNKNOWN)) {
+                        expectCCSFlight = false;
+                    }
+                }
             } else {
                 // a Finished message or other ciphertexts
-                return acquireCachedMessage();
+                plaintext = acquireCachedMessage();
             }
+
+            return plaintext;
+        }
+
+        //
+        // Reset the handshake flight from a previous one.
+        //
+        private void resetHandshakeFlight(HandshakeFlight prev) {
+            // Reset the next handshake flight.
+            handshakeFlight.handshakeType = HandshakeFlight.HF_UNKNOWN;
+            handshakeFlight.flightEpoch = prev.maxRecordEpoch;
+            if (prev.flightEpoch != prev.maxRecordEpoch) {
+                // a new epoch starts
+                handshakeFlight.minMessageSeq = 0;
+            } else {
+                // stay at the same epoch
+                //
+                // The minimal message sequence number will get updated if
+                // a flight retransmission happens.
+                handshakeFlight.minMessageSeq = prev.maxMessageSeq + 1;
+            }
+
+            // cleanup the maximum sequence number and epoch number.
+            //
+            // Note: actually, we need to do nothing because the reassembler
+            // of handshake messages will reset them properly even for
+            // retransmissions.
+            //
+            handshakeFlight.maxMessageSeq = 0;
+            handshakeFlight.maxRecordEpoch = handshakeFlight.flightEpoch;
+
+            // Record sequence number cannot wrap even for retransmissions.
+            handshakeFlight.maxRecordSeq = prev.maxRecordSeq + 1;
+
+            // cleanup holes map
+            handshakeFlight.holesMap.clear();
+
+            // Ready to accept new input record.
+            flightIsReady = false;
+            needToCheckFlight = false;
         }
 
         private Plaintext acquireCachedMessage() {
@@ -750,6 +1093,9 @@
             if (readEpoch != rFrag.recordEpoch) {
                 if (readEpoch > rFrag.recordEpoch) {
                     // discard old records
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log("Discard old buffered ciphertext fragments.");
+                    }
                     bufferedFragments.remove(rFrag);    // popup the fragment
                 }
 
@@ -757,6 +1103,10 @@
                 if (flightIsReady) {
                     flightIsReady = false;
                 }
+
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("Not yet ready to decrypt the cached fragments.");
+                }
                 return null;
             }
 
@@ -768,9 +1118,8 @@
                 plaintextFragment = decrypt(readAuthenticator, readCipher,
                         rFrag.contentType, fragment, rFrag.recordEnS);
             } catch (BadPaddingException bpe) {
-                if (debug != null && Debug.isOn("ssl")) {
-                    System.out.println(Thread.currentThread().getName() +
-                            " discard invalid record: " + bpe);
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("Discard invalid record: " + bpe);
                 }
 
                 // invalid, discard this record [section 4.1.2.7, RFC 6347]
@@ -782,7 +1131,6 @@
             // beginning of the next flight) message.  Need not to check
             // any ChangeCipherSpec message.
             if (rFrag.contentType == Record.ct_handshake) {
-                HandshakeFragment finFrag = null;
                 while (plaintextFragment.remaining() > 0) {
                     HandshakeFragment hsFrag = parseHandshakeMessage(
                             rFrag.contentType,
@@ -792,66 +1140,31 @@
 
                     if (hsFrag == null) {
                         // invalid, discard this record
+                        if (debug != null && Debug.isOn("verbose")) {
+                            Debug.printHex(
+                                    "Invalid handshake fragment, discard it",
+                                    plaintextFragment);
+                        }
                         return null;
                     }
 
-                    if (hsFrag.handshakeType == HandshakeMessage.ht_finished) {
-                        finFrag = hsFrag;
-
-                        // reset for the next flight
-                        this.flightType = (byte)0xFF;
-                        this.flightTopEpoch = rFrag.recordEpoch;
-                        this.flightTopMessageSeq = hsFrag.messageSeq + 1;
-                        this.flightTopRecordSeq = -1;
-                    } else {
-                        // reset the flight
-                        if (flightIsReady) {
-                            flightIsReady = false;
-                        }
-                        queueUpHandshake(hsFrag);
+                    queueUpHandshake(hsFrag);
+                    // The flight ready status (flightIsReady) should have
+                    // been checked and updated for the Finished handshake
+                    // message before the decryption.  Please don't update
+                    // flightIsReady for Finished messages.
+                    if (hsFrag.handshakeType != HandshakeMessage.ht_finished) {
+                        flightIsReady = false;
+                        needToCheckFlight = true;
                     }
                 }
 
-                this.nextRecordSeq = rFrag.recordSeq + 1;
-                this.nextMessageSeq = 0;
-
-                if (finFrag != null) {
-                    this.nextRecordEpoch = finFrag.recordEpoch;
-                    this.nextRecordSeq = finFrag.recordSeq + 1;
-                    this.nextMessageSeq = finFrag.messageSeq + 1;
-
-                    // Finished message does not fragment.
-                    byte[] recordFrag = new byte[finFrag.messageLength + 4];
-                    Plaintext plaintext = new Plaintext(finFrag.contentType,
-                            finFrag.majorVersion, finFrag.minorVersion,
-                            finFrag.recordEpoch, finFrag.recordSeq,
-                            ByteBuffer.wrap(recordFrag));
-
-                    // fill the handshake fragment of the record
-                    recordFrag[0] = finFrag.handshakeType;
-                    recordFrag[1] =
-                            (byte)((finFrag.messageLength >>> 16) & 0xFF);
-                    recordFrag[2] =
-                            (byte)((finFrag.messageLength >>> 8) & 0xFF);
-                    recordFrag[3] = (byte)(finFrag.messageLength & 0xFF);
-
-                    System.arraycopy(finFrag.fragment, 0,
-                            recordFrag, 4, finFrag.fragmentLength);
-
-                    // handshake hashing
-                    handshakeHashing(finFrag, plaintext);
-
-                    // input handshake finished
-                    handshakeFinished = true;
-
-                    return plaintext;
-                } else {
-                    return acquirePlaintext();
-                }
+                return acquirePlaintext();
             } else {
                 return new Plaintext(rFrag.contentType,
                         rFrag.majorVersion, rFrag.minorVersion,
-                        rFrag.recordEpoch, rFrag.recordSeq,
+                        rFrag.recordEpoch,
+                        Authenticator.toLong(rFrag.recordEnS),
                         plaintextFragment);
             }
         }
@@ -861,17 +1174,23 @@
             RecordFragment rFrag = bufferedFragments.first();
             if (rFrag.contentType == Record.ct_change_cipher_spec) {
                 this.nextRecordEpoch = rFrag.recordEpoch + 1;
+
+                // For retransmissions, the next record sequence number is a
+                // positive value.  Don't worry about it as the acquiring of
+                // the immediately followed Finished handshake message will
+                // reset the next record sequence number correctly.
                 this.nextRecordSeq = 0;
-                // no change on next handshake message sequence number
 
-                bufferedFragments.remove(rFrag);        // popup the fragment
+                // Popup the fragment.
+                bufferedFragments.remove(rFrag);
 
                 // Reload if this message has been reserved for handshake hash.
                 handshakeHash.reload();
 
                 return new Plaintext(rFrag.contentType,
                         rFrag.majorVersion, rFrag.minorVersion,
-                        rFrag.recordEpoch, rFrag.recordSeq,
+                        rFrag.recordEpoch,
+                        Authenticator.toLong(rFrag.recordEnS),
                         ByteBuffer.wrap(rFrag.fragment));
             } else {    // rFrag.contentType == Record.ct_handshake
                 HandshakeFragment hsFrag = (HandshakeFragment)rFrag;
@@ -882,13 +1201,13 @@
 
                     // this.nextRecordEpoch = hsFrag.recordEpoch;
                     this.nextRecordSeq = hsFrag.recordSeq + 1;
-                    this.nextMessageSeq = hsFrag.messageSeq + 1;
 
                     // Note: may try to avoid byte array copy in the future.
                     byte[] recordFrag = new byte[hsFrag.messageLength + 4];
                     Plaintext plaintext = new Plaintext(hsFrag.contentType,
                             hsFrag.majorVersion, hsFrag.minorVersion,
-                            hsFrag.recordEpoch, hsFrag.recordSeq,
+                            hsFrag.recordEpoch,
+                            Authenticator.toLong(hsFrag.recordEnS),
                             ByteBuffer.wrap(recordFrag));
 
                     // fill the handshake fragment of the record
@@ -913,7 +1232,8 @@
                     byte[] recordFrag = new byte[hsFrag.messageLength + 4];
                     Plaintext plaintext = new Plaintext(hsFrag.contentType,
                             hsFrag.majorVersion, hsFrag.minorVersion,
-                            hsFrag.recordEpoch, hsFrag.recordSeq,
+                            hsFrag.recordEpoch,
+                            Authenticator.toLong(hsFrag.recordEnS),
                             ByteBuffer.wrap(recordFrag));
 
                     // fill the handshake fragment of the record
@@ -957,7 +1277,6 @@
                     handshakeHashing(hsFrag, plaintext);
 
                     this.nextRecordSeq = maxRecodeSN + 1;
-                    this.nextMessageSeq = msgSeq + 1;
 
                     return plaintext;
                 }
@@ -966,15 +1285,26 @@
 
         boolean flightIsReady() {
 
-            //
-            // the ChangeCipherSpec/Finished flight
-            //
-            if (expectCCSFlight) {
-                // Have the ChangeCipherSpec/Finished messages been received?
-                return hasFinisedMessage(bufferedFragments);
-            }
+            byte flightType = handshakeFlight.handshakeType;
+            if (flightType == HandshakeFlight.HF_UNKNOWN) {
+                //
+                // the ChangeCipherSpec/Finished flight
+                //
+                if (expectCCSFlight) {
+                    // Have the ChangeCipherSpec/Finished flight been received?
+                    boolean isReady = hasFinishedMessage(bufferedFragments);
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log(
+                            "Has the final flight been received? " + isReady);
+                    }
 
-            if (flightType == (byte)0xFF) {
+                    return isReady;
+                }
+
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("No flight is received yet.");
+                }
+
                 return false;
             }
 
@@ -983,7 +1313,12 @@
                 (flightType == HandshakeMessage.ht_hello_verify_request)) {
 
                 // single handshake message flight
-                return hasCompleted(holesMap.get(flightType));
+                boolean isReady = hasCompleted(flightType);
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("Is the handshake message completed? " + isReady);
+                }
+
+                return isReady;
             }
 
             //
@@ -991,31 +1326,52 @@
             //
             if (flightType == HandshakeMessage.ht_server_hello) {
                 // Firstly, check the first flight handshake message.
-                if (!hasCompleted(holesMap.get(flightType))) {
+                if (!hasCompleted(flightType)) {
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log(
+                            "The ServerHello message is not completed yet.");
+                    }
+
                     return false;
                 }
 
                 //
                 // an abbreviated handshake
                 //
-                if (isAbbreviatedHandshake) {
-                    // Ready to use the flight if received the
-                    // ChangeCipherSpec and Finished messages.
-                    return hasFinisedMessage(bufferedFragments);
+                if (hasFinishedMessage(bufferedFragments)) {
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log("It's an abbreviated handshake.");
+                    }
+
+                    return true;
                 }
 
                 //
                 // a full handshake
                 //
-                if (lastHandshakeFragment.handshakeType !=
-                        HandshakeMessage.ht_server_hello_done) {
+                List<HoleDescriptor> holes = handshakeFlight.holesMap.get(
+                        HandshakeMessage.ht_server_hello_done);
+                if ((holes == null) || !holes.isEmpty()) {
                     // Not yet got the final message of the flight.
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log("Not yet got the ServerHelloDone message");
+                    }
+
                     return false;
                 }
 
                 // Have all handshake message been received?
-                return hasCompleted(bufferedFragments,
-                    flightTopMessageSeq, lastHandshakeFragment.messageSeq);
+                boolean isReady = hasCompleted(bufferedFragments,
+                            handshakeFlight.minMessageSeq,
+                            handshakeFlight.maxMessageSeq);
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("Is the ServerHello flight (message " +
+                            handshakeFlight.minMessageSeq + "-" +
+                            handshakeFlight.maxMessageSeq +
+                            ") completed? " + isReady);
+                }
+
+                return isReady;
             }
 
             //
@@ -1029,92 +1385,65 @@
                 (flightType == HandshakeMessage.ht_client_key_exchange)) {
 
                 // Firstly, check the first flight handshake message.
-                if (!hasCompleted(holesMap.get(flightType))) {
+                if (!hasCompleted(flightType)) {
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log(
+                            "The ClientKeyExchange or client Certificate " +
+                            "message is not completed yet.");
+                    }
+
                     return false;
                 }
 
-                if (!hasFinisedMessage(bufferedFragments)) {
-                    // not yet got the ChangeCipherSpec/Finished messages
-                    return false;
+                // Is client CertificateVerify a mandatory message?
+                if (flightType == HandshakeMessage.ht_certificate) {
+                    if (needClientVerify(bufferedFragments) &&
+                        !hasCompleted(ht_certificate_verify)) {
+
+                        if (debug != null && Debug.isOn("verbose")) {
+                            Debug.log(
+                                "Not yet have the CertificateVerify message");
+                        }
+
+                        return false;
+                    }
                 }
 
-                if (flightType == HandshakeMessage.ht_client_key_exchange) {
-                    // single handshake message flight
-                    return true;
-                }
+                if (!hasFinishedMessage(bufferedFragments)) {
+                    // not yet have the ChangeCipherSpec/Finished messages
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log(
+                            "Not yet have the ChangeCipherSpec and " +
+                            "Finished messages");
+                    }
 
-                //
-                // flightType == HandshakeMessage.ht_certificate
-                //
-                // We don't support certificates containing fixed
-                // Diffie-Hellman parameters.  Therefore, CertificateVerify
-                // message is required if client Certificate message presents.
-                //
-                if (lastHandshakeFragment.handshakeType !=
-                        HandshakeMessage.ht_certificate_verify) {
-                    // Not yet got the final message of the flight.
                     return false;
                 }
 
                 // Have all handshake message been received?
-                return hasCompleted(bufferedFragments,
-                    flightTopMessageSeq, lastHandshakeFragment.messageSeq);
+                boolean isReady = hasCompleted(bufferedFragments,
+                            handshakeFlight.minMessageSeq,
+                            handshakeFlight.maxMessageSeq);
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log("Is the ClientKeyExchange flight (message " +
+                            handshakeFlight.minMessageSeq + "-" +
+                            handshakeFlight.maxMessageSeq +
+                            ") completed? " + isReady);
+                }
+
+                return isReady;
             }
 
             //
             // Otherwise, need to receive more handshake messages.
             //
-            return false;
-        }
-
-        private boolean isSessionResuming(
-                byte[] fragment, byte[] prevSid) throws SSLException {
-
-            // As the first fragment of ServerHello should be big enough
-            // to hold the session_id field, need not to worry about the
-            // fragmentation here.
-            if ((fragment == null) || (fragment.length < 38)) {
-                                    // 38: the minimal ServerHello body length
-                throw new SSLException(
-                        "Invalid ServerHello message: no sufficient data");
-            }
-
-            int sidLen = fragment[34];          // 34: the length field
-            if (sidLen > 32) {                  // opaque SessionID<0..32>
-                throw new SSLException(
-                        "Invalid ServerHello message: invalid session id");
-            }
-
-            if (fragment.length < 38 + sidLen) {
-                throw new SSLException(
-                        "Invalid ServerHello message: no sufficient data");
-            }
-
-            if (sidLen != 0 && (prevSid.length == sidLen)) {
-                // may be a session-resuming handshake
-                for (int i = 0; i < sidLen; i++) {
-                    if (prevSid[i] != fragment[35 + i]) {
-                                                // 35: the session identifier
-                        return false;
-                    }
-                }
-
-                return true;
+            if (debug != null && Debug.isOn("verbose")) {
+                Debug.log("Need to receive more handshake messages");
             }
 
             return false;
         }
 
-        private byte[] getSessionID(byte[] fragment) {
-            // The validity has been checked in the call to isSessionResuming().
-            int sidLen = fragment[34];      // 34: the sessionID length field
-
-            byte[] temporary = new byte[sidLen];
-            System.arraycopy(fragment, 35, temporary, 0, sidLen);
-
-            return temporary;
-        }
-
         // Looking for the ChangeCipherSpec and Finished messages.
         //
         // As the cached Finished message should be a ciphertext, we don't
@@ -1122,8 +1451,7 @@
         // to the spec of TLS/DTLS handshaking, a Finished message is always
         // sent immediately after a ChangeCipherSpec message.  The first
         // ciphertext handshake message should be the expected Finished message.
-        private boolean hasFinisedMessage(
-                Set<RecordFragment> fragments) {
+        private boolean hasFinishedMessage(Set<RecordFragment> fragments) {
 
             boolean hasCCS = false;
             boolean hasFin = false;
@@ -1147,7 +1475,35 @@
             return hasFin && hasCCS;
         }
 
-        private boolean hasCompleted(List<HoleDescriptor> holes) {
+        // Is client CertificateVerify a mandatory message?
+        //
+        // In the current implementation, client CertificateVerify is a
+        // mandatory message if the client Certificate is not empty.
+        private boolean needClientVerify(Set<RecordFragment> fragments) {
+
+            // The caller should have checked the completion of the first
+            // present handshake message.  Need not to check it again.
+            for (RecordFragment rFrag : fragments) {
+                if ((rFrag.contentType != Record.ct_handshake) ||
+                        rFrag.isCiphertext) {
+                    break;
+                }
+
+                HandshakeFragment hsFrag = (HandshakeFragment)rFrag;
+                if (hsFrag.handshakeType != HandshakeMessage.ht_certificate) {
+                    continue;
+                }
+
+                return (rFrag.fragment != null) &&
+                   (rFrag.fragment.length > DTLSRecord.minCertPlaintextSize);
+            }
+
+            return false;
+        }
+
+        private boolean hasCompleted(byte handshakeType) {
+            List<HoleDescriptor> holes =
+                    handshakeFlight.holesMap.get(handshakeType);
             if (holes == null) {
                 // not yet received this kind of handshake message
                 return false;
@@ -1173,7 +1529,7 @@
                     continue;
                 } else if (hsFrag.messageSeq == (presentMsgSeq + 1)) {
                     // check the completion of the handshake message
-                    if (!hasCompleted(holesMap.get(hsFrag.handshakeType))) {
+                    if (!hasCompleted(hsFrag.handshakeType)) {
                         return false;
                     }
 
--- a/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java	Fri Oct 28 10:10:06 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java	Sat Oct 29 13:34:53 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -279,6 +279,16 @@
         fragmenter = null;
     }
 
+    @Override
+    void launchRetransmission() {
+        // Note: Please don't retransmit if there are handshake messages
+        // or alerts waiting in the queue.
+        if (((alertMemos == null) || alertMemos.isEmpty()) &&
+                (fragmenter != null) && fragmenter.isRetransmittable()) {
+            fragmenter.setRetransmission();
+        }
+    }
+
     // buffered record fragment
     private static class RecordMemo {
         byte            contentType;
--- a/src/java.base/share/classes/sun/security/ssl/DTLSRecord.java	Fri Oct 28 10:10:06 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/DTLSRecord.java	Sat Oct 29 13:34:53 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -84,4 +84,18 @@
                                     + maxPadding            // padding
                                     + maxMacSize;           // MAC
 
+    /*
+     * Minimum record size of Certificate handshake message.
+     * Client sends a certificate message containing no certificates if no
+     * suitable certificate is available.  That is, the certificate_list
+     * structure has a length of zero.
+     *
+     *   struct {
+     *       ASN.1Cert certificate_list<0..2^24-1>;
+     *   } Certificate;
+     */
+    static final int    minCertPlaintextSize =
+                                      headerSize            // record header
+                                    + handshakeHeaderSize   // handshake header
+                                    + 3;                    // cert list length
 }
--- a/src/java.base/share/classes/sun/security/ssl/Debug.java	Fri Oct 28 10:10:06 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/Debug.java	Sat Oct 29 13:34:53 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -145,6 +145,13 @@
     }
 
     /**
+     * Print a message to stdout.
+     */
+    static void log(String message) {
+        System.out.println(Thread.currentThread().getName() + ": " + message);
+    }
+
+    /**
      * print a blank line to stderr that is prefixed with the prefix.
      */
 
@@ -156,7 +163,6 @@
     /**
      * print a message to stderr that is prefixed with the prefix.
      */
-
     public static void println(String prefix, String message)
     {
         System.err.println(prefix + ": "+message);
--- a/src/java.base/share/classes/sun/security/ssl/OutputRecord.java	Fri Oct 28 10:10:06 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/OutputRecord.java	Sat Oct 29 13:34:53 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -194,6 +194,11 @@
         // blank
     }
 
+    // apply to DTLS SSLEngine
+    void launchRetransmission() {
+        // blank
+    }
+
     @Override
     public synchronized void close() throws IOException {
         if (!isClosed) {
@@ -224,6 +229,9 @@
             sequenceNumber = authenticator.sequenceNumber();
         }
 
+        // The sequence number may be shared for different purpose.
+        boolean sharedSequenceNumber = false;
+
         // "flip" but skip over header again, add MAC & encrypt
         if (authenticator instanceof MAC) {
             MAC signer = (MAC)authenticator;
@@ -243,6 +251,11 @@
                 // reset the position and limit
                 destination.limit(destination.position());
                 destination.position(dstContent);
+
+                // The signer has used and increased the sequence number.
+                if (isDTLS) {
+                    sharedSequenceNumber = true;
+                }
             }
         }
 
@@ -261,6 +274,11 @@
 
             // Encrypt may pad, so again the limit may be changed.
             encCipher.encrypt(destination, dstLim);
+
+            // The cipher has used and increased the sequence number.
+            if (isDTLS && encCipher.isAEADMode()) {
+                sharedSequenceNumber = true;
+            }
         } else {
             destination.position(destination.limit());
         }
@@ -290,8 +308,10 @@
             destination.put(headerOffset + 11, (byte)(fragLen >> 8));
             destination.put(headerOffset + 12, (byte)fragLen);
 
-            // Increase the sequence number for next use.
-            authenticator.increaseSequenceNumber();
+            // Increase the sequence number for next use if it is not shared.
+            if (!sharedSequenceNumber) {
+                authenticator.increaseSequenceNumber();
+            }
         }
 
         // Update destination position to reflect the amount of data produced.
--- a/src/java.base/share/classes/sun/security/ssl/Plaintext.java	Fri Oct 28 10:10:06 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/Plaintext.java	Sat Oct 29 13:34:53 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -38,7 +38,7 @@
     byte            majorVersion;
     byte            minorVersion;
     int             recordEpoch;    // incremented on every cipher state change
-    long            recordSN;
+    long            recordSN;       // contains epcoh number (epoch | sequence)
     ByteBuffer      fragment;       // null if need to be reassembled
 
     HandshakeStatus handshakeStatus;    // null if not used or not handshaking
--- a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java	Fri Oct 28 10:10:06 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java	Sat Oct 29 13:34:53 2016 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -994,7 +994,22 @@
 
         // plainText should never be null for TLS protocols
         HandshakeStatus hsStatus = null;
-        if (!isDTLS || plainText != null) {
+        if (plainText == Plaintext.PLAINTEXT_NULL) {
+            // Only happens for DTLS protocols.
+            //
+            // Received a retransmitted flight, and need to retransmit the
+            // previous delivered handshake flight messages.
+            if (enableRetransmissions) {
+                if (debug != null && Debug.isOn("verbose")) {
+                    Debug.log(
+                        "Retransmit the previous handshake flight messages.");
+                }
+
+                synchronized (this) {
+                    outputRecord.launchRetransmission();
+                }
+            }   // Otherwise, discard the retransmitted flight.
+        } else if (!isDTLS || plainText != null) {
             hsStatus = processInputRecord(plainText, appData, offset, length);
         }
 
@@ -1003,7 +1018,7 @@
         }
 
         if (plainText == null) {
-            plainText = new Plaintext();
+            plainText = Plaintext.PLAINTEXT_NULL;
         }
         plainText.handshakeStatus = hsStatus;
 
@@ -1378,7 +1393,8 @@
             // Acquire the buffered to-be-delivered records or retransmissions.
             //
             // May have buffered records, or need retransmission if handshaking.
-            if (!outputRecord.isEmpty() || (handshaker != null)) {
+            if (!outputRecord.isEmpty() ||
+                    (enableRetransmissions && handshaker != null)) {
                 ciphertext = outputRecord.acquireCiphertext(netData);
             }
 
@@ -1403,13 +1419,36 @@
 
         HandshakeStatus hsStatus = null;
         Ciphertext.RecordType recordType = ciphertext.recordType;
-        if ((handshaker != null) &&
-                (recordType.contentType == Record.ct_handshake) &&
-                (recordType.handshakeType == HandshakeMessage.ht_finished) &&
-                handshaker.isDone() && outputRecord.isEmpty()) {
+        if ((recordType.contentType == Record.ct_handshake) &&
+            (recordType.handshakeType == HandshakeMessage.ht_finished) &&
+            outputRecord.isEmpty()) {
 
-            hsStatus = finishHandshake();
-            connectionState = cs_DATA;
+            if (handshaker == null) {
+                hsStatus = HandshakeStatus.FINISHED;
+            } else if (handshaker.isDone()) {
+                hsStatus = finishHandshake();
+                connectionState = cs_DATA;
+
+                // Retransmit the last flight twice.
+                //
+                // The application data transactions may begin immediately
+                // after the last flight.  If the last flight get lost, the
+                // application data may be discarded accordingly.  As could
+                // be an issue for some applications.  This impact can be
+                // mitigated by sending the last fligth twice.
+                if (isDTLS && enableRetransmissions) {
+                    if (debug != null && Debug.isOn("verbose")) {
+                        Debug.log(
+                            "Retransmit the last flight messages.");
+                    }
+
+                    synchronized (this) {
+                        outputRecord.launchRetransmission();
+                    }
+
+                    hsStatus = HandshakeStatus.NEED_WRAP;
+                }
+            }
         }   // Otherwise, the followed call to getHSStatus() will help.
 
         /*
--- a/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java	Fri Oct 28 10:10:06 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java	Sat Oct 29 13:34:53 2016 +0000
@@ -558,73 +558,6 @@
             applicationProtocol = "";
         }
 
-        // cookie exchange
-        if (isDTLS) {
-             HelloCookieManager hcMgr = sslContext.getHelloCookieManager();
-             if ((mesg.cookie == null) || (mesg.cookie.length == 0) ||
-                    (!hcMgr.isValid(mesg))) {
-
-                //
-                // Perform cookie exchange for DTLS handshaking if no cookie
-                // or the cookie is invalid in the ClientHello message.
-                //
-                HelloVerifyRequest m0 = new HelloVerifyRequest(hcMgr, mesg);
-
-                if (debug != null && Debug.isOn("handshake")) {
-                    m0.print(System.out);
-                }
-
-                m0.write(output);
-                handshakeState.update(m0, resumingSession);
-                output.flush();
-
-                return;
-            }
-        }
-
-        /*
-         * FIRST, construct the ServerHello using the options and priorities
-         * from the ClientHello.  Update the (pending) cipher spec as we do
-         * so, and save the client's version to protect against rollback
-         * attacks.
-         *
-         * There are a bunch of minor tasks here, and one major one: deciding
-         * if the short or the full handshake sequence will be used.
-         */
-        ServerHello m1 = new ServerHello();
-
-        clientRequestedVersion = mesg.protocolVersion;
-
-        // select a proper protocol version.
-        ProtocolVersion selectedVersion =
-               selectProtocolVersion(clientRequestedVersion);
-        if (selectedVersion == null ||
-                selectedVersion.v == ProtocolVersion.SSL20Hello.v) {
-            fatalSE(Alerts.alert_handshake_failure,
-                "Client requested protocol " + clientRequestedVersion +
-                " not enabled or not supported");
-        }
-
-        handshakeHash.protocolDetermined(selectedVersion);
-        setVersion(selectedVersion);
-
-        m1.protocolVersion = protocolVersion;
-
-        //
-        // random ... save client and server values for later use
-        // in computing the master secret (from pre-master secret)
-        // and thence the other crypto keys.
-        //
-        // NOTE:  this use of three inputs to generating _each_ set
-        // of ciphers slows things down, but it does increase the
-        // security since each connection in the session can hold
-        // its own authenticated (and strong) keys.  One could make
-        // creation of a session a rare thing...
-        //
-        clnt_random = mesg.clnt_random;
-        svr_random = new RandomCookie(sslContext.getSecureRandom());
-        m1.svr_random = svr_random;
-
         session = null; // forget about the current session
         //
         // Here we go down either of two paths:  (a) the fast one, where
@@ -732,6 +665,73 @@
             }
         }   // else client did not try to resume
 
+        // cookie exchange
+        if (isDTLS && !resumingSession) {
+             HelloCookieManager hcMgr = sslContext.getHelloCookieManager();
+             if ((mesg.cookie == null) || (mesg.cookie.length == 0) ||
+                    (!hcMgr.isValid(mesg))) {
+
+                //
+                // Perform cookie exchange for DTLS handshaking if no cookie
+                // or the cookie is invalid in the ClientHello message.
+                //
+                HelloVerifyRequest m0 = new HelloVerifyRequest(hcMgr, mesg);
+
+                if (debug != null && Debug.isOn("handshake")) {
+                    m0.print(System.out);
+                }
+
+                m0.write(output);
+                handshakeState.update(m0, resumingSession);
+                output.flush();
+
+                return;
+            }
+        }
+
+        /*
+         * FIRST, construct the ServerHello using the options and priorities
+         * from the ClientHello.  Update the (pending) cipher spec as we do
+         * so, and save the client's version to protect against rollback
+         * attacks.
+         *
+         * There are a bunch of minor tasks here, and one major one: deciding
+         * if the short or the full handshake sequence will be used.
+         */
+        ServerHello m1 = new ServerHello();
+
+        clientRequestedVersion = mesg.protocolVersion;
+
+        // select a proper protocol version.
+        ProtocolVersion selectedVersion =
+               selectProtocolVersion(clientRequestedVersion);
+        if (selectedVersion == null ||
+                selectedVersion.v == ProtocolVersion.SSL20Hello.v) {
+            fatalSE(Alerts.alert_handshake_failure,
+                "Client requested protocol " + clientRequestedVersion +
+                " not enabled or not supported");
+        }
+
+        handshakeHash.protocolDetermined(selectedVersion);
+        setVersion(selectedVersion);
+
+        m1.protocolVersion = protocolVersion;
+
+        //
+        // random ... save client and server values for later use
+        // in computing the master secret (from pre-master secret)
+        // and thence the other crypto keys.
+        //
+        // NOTE:  this use of three inputs to generating _each_ set
+        // of ciphers slows things down, but it does increase the
+        // security since each connection in the session can hold
+        // its own authenticated (and strong) keys.  One could make
+        // creation of a session a rare thing...
+        //
+        clnt_random = mesg.clnt_random;
+        svr_random = new RandomCookie(sslContext.getSecureRandom());
+        m1.svr_random = svr_random;
+
         //
         // If client hasn't specified a session we can resume, start a
         // new one and choose its cipher suite and compression options.
--- a/test/javax/net/ssl/DTLS/DTLSOverDatagram.java	Fri Oct 28 10:10:06 2016 +0530
+++ b/test/javax/net/ssl/DTLS/DTLSOverDatagram.java	Sat Oct 29 13:34:53 2016 +0000
@@ -48,10 +48,6 @@
  */
 public class DTLSOverDatagram {
 
-    static {
-        System.setProperty("javax.net.debug", "ssl");
-    }
-
     private static int MAX_HANDSHAKE_LOOPS = 200;
     private static int MAX_APP_READ_LOOPS = 60;
     private static int SOCKET_TIMEOUT = 10 * 1000; // in millis
@@ -160,6 +156,7 @@
             }
 
             SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
+            log(side, "=======handshake(" + loops + ", " + hs + ")=======");
             if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
                 hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
 
@@ -239,6 +236,7 @@
                 boolean finished = produceHandshakePackets(
                     engine, peerAddr, side, packets);
 
+                log(side, "Produced " + packets.size() + " packets");
                 for (DatagramPacket p : packets) {
                     socket.send(p);
                 }
@@ -252,14 +250,16 @@
             } else if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
                 runDelegatedTasks(engine);
             } else if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
-                log(side, "Handshake status is NOT_HANDSHAKING, finish the loop");
+                log(side,
+                    "Handshake status is NOT_HANDSHAKING, finish the loop");
                 endLoops = true;
             } else if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
                 throw new Exception(
                         "Unexpected status, SSLEngine.getHandshakeStatus() "
                                 + "shouldn't return FINISHED");
             } else {
-                throw new Exception("Can't reach here, handshake status is " + hs);
+                throw new Exception(
+                        "Can't reach here, handshake status is " + hs);
             }
         }
 
@@ -279,7 +279,9 @@
         log(side, "Negotiated cipher suite is " + session.getCipherSuite());
 
         // handshake status should be NOT_HANDSHAKING
-        // according to the spec, SSLEngine.getHandshakeStatus() can't return FINISHED
+        //
+        // According to the spec, SSLEngine.getHandshakeStatus() can't
+        // return FINISHED.
         if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
             throw new Exception("Unexpected handshake status " + hs);
         }
@@ -348,13 +350,16 @@
 
             SSLEngineResult.Status rs = r.getStatus();
             SSLEngineResult.HandshakeStatus hs = r.getHandshakeStatus();
+            log(side, "====packet(" + loops + ", " + rs + ", " + hs + ")====");
             if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
                 // the client maximum fragment size config does not work?
                 throw new Exception("Buffer overflow: " +
                             "incorrect server maximum fragment size");
             } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
-                log(side, "Produce handshake packets: BUFFER_UNDERFLOW occured");
-                log(side, "Produce handshake packets: Handshake status: " + hs);
+                log(side,
+                        "Produce handshake packets: BUFFER_UNDERFLOW occured");
+                log(side,
+                        "Produce handshake packets: Handshake status: " + hs);
                 // bad packet, or the client maximum fragment size
                 // config does not work?
                 if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
@@ -453,6 +458,53 @@
         return packets;
     }
 
+    // Get a datagram packet for the specified handshake type.
+    static DatagramPacket getPacket(
+            List<DatagramPacket> packets, byte handshakeType) {
+        boolean matched = false;
+        for (DatagramPacket packet : packets) {
+            byte[] data = packet.getData();
+            int offset = packet.getOffset();
+            int length = packet.getLength();
+
+            // Normally, this pakcet should be a handshake message
+            // record.  However, even if the underlying platform
+            // splits the record more, we don't really worry about
+            // the improper packet loss because DTLS implementation
+            // should be able to handle packet loss properly.
+            //
+            // See RFC 6347 for the detailed format of DTLS records.
+            if (handshakeType == -1) {      // ChangeCipherSpec
+                // Is it a ChangeCipherSpec message?
+                matched = (length == 14) && (data[offset] == 0x14);
+            } else if ((length >= 25) &&    // 25: handshake mini size
+                (data[offset] == 0x16)) {   // a handshake message
+
+                // check epoch number for initial handshake only
+                if (data[offset + 3] == 0x00) {     // 3,4: epoch
+                    if (data[offset + 4] == 0x00) { // plaintext
+                        matched =
+                            (data[offset + 13] == handshakeType);
+                    } else {                        // cipherext
+                        // The 1st ciphertext is a Finished message.
+                        //
+                        // If it is not proposed to loss the Finished
+                        // message, it is not necessary to check the
+                        // following packets any mroe as a Finished
+                        // message is the last handshake message.
+                        matched = (handshakeType == 20);
+                    }
+                }
+            }
+
+            if (matched) {
+                return packet;
+            }
+        }
+
+        return null;
+    }
+
     // run delegated tasks
     void runDelegatedTasks(SSLEngine engine) throws Exception {
         Runnable runnable;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/net/ssl/DTLS/PacketLossRetransmission.java	Sat Oct 29 13:34:53 2016 +0000
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8161086
+ * @summary DTLS handshaking fails if some messages were lost
+ * @modules java.base/sun.security.util
+ * @build DTLSOverDatagram
+ *
+ * @run main/othervm PacketLossRetransmission client 0 hello_request
+ * @run main/othervm PacketLossRetransmission client 1 client_hello
+ * @run main/othervm PacketLossRetransmission client 2 server_hello
+ * @run main/othervm PacketLossRetransmission client 3 hello_verify_request
+ * @run main/othervm PacketLossRetransmission client 4 new_session_ticket
+ * @run main/othervm PacketLossRetransmission client 11 certificate
+ * @run main/othervm PacketLossRetransmission client 12 server_key_exchange
+ * @run main/othervm PacketLossRetransmission client 13 certificate_request
+ * @run main/othervm PacketLossRetransmission client 14 server_hello_done
+ * @run main/othervm PacketLossRetransmission client 15 certificate_verify
+ * @run main/othervm PacketLossRetransmission client 16 client_key_exchange
+ * @run main/othervm PacketLossRetransmission client 20 finished
+ * @run main/othervm PacketLossRetransmission client 21 certificate_url
+ * @run main/othervm PacketLossRetransmission client 22 certificate_status
+ * @run main/othervm PacketLossRetransmission client 23 supplemental_data
+ * @run main/othervm PacketLossRetransmission client -1 change_cipher_spec
+ * @run main/othervm PacketLossRetransmission server 0 hello_request
+ * @run main/othervm PacketLossRetransmission server 1 client_hello
+ * @run main/othervm PacketLossRetransmission server 2 server_hello
+ * @run main/othervm PacketLossRetransmission server 3 hello_verify_request
+ * @run main/othervm PacketLossRetransmission server 4 new_session_ticket
+ * @run main/othervm PacketLossRetransmission server 11 certificate
+ * @run main/othervm PacketLossRetransmission server 12 server_key_exchange
+ * @run main/othervm PacketLossRetransmission server 13 certificate_request
+ * @run main/othervm PacketLossRetransmission server 14 server_hello_done
+ * @run main/othervm PacketLossRetransmission server 15 certificate_verify
+ * @run main/othervm PacketLossRetransmission server 16 client_key_exchange
+ * @run main/othervm PacketLossRetransmission server 20 finished
+ * @run main/othervm PacketLossRetransmission server 21 certificate_url
+ * @run main/othervm PacketLossRetransmission server 22 certificate_status
+ * @run main/othervm PacketLossRetransmission server 23 supplemental_data
+ * @run main/othervm PacketLossRetransmission server -1 change_cipher_spec
+ */
+
+import java.util.List;
+import java.util.ArrayList;
+import java.net.DatagramPacket;
+import java.net.SocketAddress;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Test that DTLS implementation is able to do retransmission internally
+ * automatically if packet get lost.
+ */
+public class PacketLossRetransmission extends DTLSOverDatagram {
+    private static boolean isClient;
+    private static byte handshakeType;
+
+    private boolean needPacketLoss = true;
+
+    public static void main(String[] args) throws Exception {
+        isClient = args[0].equals("client");
+        handshakeType = Byte.valueOf(args[1]);
+
+        PacketLossRetransmission testCase = new PacketLossRetransmission();
+        testCase.runTest(testCase);
+    }
+
+    @Override
+    boolean produceHandshakePackets(SSLEngine engine, SocketAddress socketAddr,
+            String side, List<DatagramPacket> packets) throws Exception {
+
+        boolean finished = super.produceHandshakePackets(
+                engine, socketAddr, side, packets);
+
+        if (needPacketLoss && (!(isClient ^ engine.getUseClientMode()))) {
+            DatagramPacket packet = getPacket(packets, handshakeType);
+            if (packet != null) {
+                needPacketLoss = false;
+
+                System.out.println("Loss a packet of handshake messahe");
+                packets.remove(packet);
+            }
+        }
+
+        return finished;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/net/ssl/DTLS/RespondToRetransmit.java	Sat Oct 29 13:34:53 2016 +0000
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+
+/*
+ * @test
+ * @bug 8161086
+ * @summary DTLS handshaking fails if some messages were lost
+ * @modules java.base/sun.security.util
+ * @build DTLSOverDatagram
+ *
+ * @run main/othervm RespondToRetransmit client 0 hello_request
+ * @run main/othervm RespondToRetransmit client 1 client_hello
+ * @run main/othervm RespondToRetransmit client 2 server_hello
+ * @run main/othervm RespondToRetransmit client 3 hello_verify_request
+ * @run main/othervm RespondToRetransmit client 4 new_session_ticket
+ * @run main/othervm RespondToRetransmit client 11 certificate
+ * @run main/othervm RespondToRetransmit client 12 server_key_exchange
+ * @run main/othervm RespondToRetransmit client 13 certificate_request
+ * @run main/othervm RespondToRetransmit client 14 server_hello_done
+ * @run main/othervm RespondToRetransmit client 15 certificate_verify
+ * @run main/othervm RespondToRetransmit client 16 client_key_exchange
+ * @run main/othervm RespondToRetransmit client 20 finished
+ * @run main/othervm RespondToRetransmit client 21 certificate_url
+ * @run main/othervm RespondToRetransmit client 22 certificate_status
+ * @run main/othervm RespondToRetransmit client 23 supplemental_data
+ * @run main/othervm RespondToRetransmit client -1 change_cipher_spec
+ * @run main/othervm RespondToRetransmit server 0 hello_request
+ * @run main/othervm RespondToRetransmit server 1 client_hello
+ * @run main/othervm RespondToRetransmit server 2 server_hello
+ * @run main/othervm RespondToRetransmit server 3 hello_verify_request
+ * @run main/othervm RespondToRetransmit server 4 new_session_ticket
+ * @run main/othervm RespondToRetransmit server 11 certificate
+ * @run main/othervm RespondToRetransmit server 12 server_key_exchange
+ * @run main/othervm RespondToRetransmit server 13 certificate_request
+ * @run main/othervm RespondToRetransmit server 14 server_hello_done
+ * @run main/othervm RespondToRetransmit server 15 certificate_verify
+ * @run main/othervm RespondToRetransmit server 16 client_key_exchange
+ * @run main/othervm RespondToRetransmit server 20 finished
+ * @run main/othervm RespondToRetransmit server 21 certificate_url
+ * @run main/othervm RespondToRetransmit server 22 certificate_status
+ * @run main/othervm RespondToRetransmit server 23 supplemental_data
+ * @run main/othervm RespondToRetransmit server -1 change_cipher_spec
+ */
+
+import java.util.List;
+import java.util.ArrayList;
+import java.net.DatagramPacket;
+import java.net.SocketAddress;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Test that DTLS implementation is able to do retransmission internally
+ * automatically if packet get lost.
+ */
+public class RespondToRetransmit extends DTLSOverDatagram {
+    private static boolean isClient;
+    private static byte handshakeType;
+
+    private boolean needPacketDuplicate = true;
+
+    public static void main(String[] args) throws Exception {
+        isClient = args[0].equals("client");
+        handshakeType = Byte.valueOf(args[1]);
+
+        RespondToRetransmit testCase = new RespondToRetransmit();
+        testCase.runTest(testCase);
+    }
+
+    @Override
+    boolean produceHandshakePackets(SSLEngine engine, SocketAddress socketAddr,
+            String side, List<DatagramPacket> packets) throws Exception {
+
+        boolean finished = super.produceHandshakePackets(
+                engine, socketAddr, side, packets);
+
+        if (needPacketDuplicate && (!(isClient ^ engine.getUseClientMode()))) {
+            DatagramPacket packet = getPacket(packets, handshakeType);
+            if (packet != null) {
+                needPacketDuplicate = false;
+
+                System.out.println("Duplicate the flight.");
+                List<DatagramPacket> duplicates = new ArrayList<>();
+                finished = super.produceHandshakePackets(
+                        engine, socketAddr, side, duplicates);
+                packets.addAll(duplicates);
+            }
+        }
+
+        return finished;
+    }
+}
--- a/test/javax/net/ssl/TLSCommon/SSLEngineTestCase.java	Fri Oct 28 10:10:06 2016 +0530
+++ b/test/javax/net/ssl/TLSCommon/SSLEngineTestCase.java	Sat Oct 29 13:34:53 2016 +0000
@@ -27,7 +27,9 @@
 import javax.net.ssl.SNIServerName;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLParameters;
 import javax.net.ssl.TrustManagerFactory;
@@ -57,19 +59,21 @@
     public enum Ciphers {
 
         /**
-         * Ciphers supported by the tested SSLEngine without those with kerberos
-         * authentication.
+         * Ciphers supported by the tested SSLEngine without those with
+         * kerberos authentication.
          */
         SUPPORTED_NON_KRB_CIPHERS(SSLEngineTestCase.SUPPORTED_NON_KRB_CIPHERS,
                 "Supported non kerberos"),
         /**
-         * Ciphers supported by the tested SSLEngine without those with kerberos
-         * authentication and without those with SHA256 ans SHA384.
+         * Ciphers supported by the tested SSLEngine without those with
+         * kerberos authentication and without those with SHA256 ans SHA384.
          */
-        SUPPORTED_NON_KRB_NON_SHA_CIPHERS(SSLEngineTestCase.SUPPORTED_NON_KRB_NON_SHA_CIPHERS,
+        SUPPORTED_NON_KRB_NON_SHA_CIPHERS(
+                SSLEngineTestCase.SUPPORTED_NON_KRB_NON_SHA_CIPHERS,
                 "Supported non kerberos non SHA256 and SHA384"),
         /**
-         * Ciphers supported by the tested SSLEngine with kerberos authentication.
+         * Ciphers supported by the tested SSLEngine with kerberos
+         * authentication.
          */
         SUPPORTED_KRB_CIPHERS(SSLEngineTestCase.SUPPORTED_KRB_CIPHERS,
                 "Supported kerberos"),
@@ -147,13 +151,13 @@
             = System.getProperty("test.src", ".") + FS + PATH_TO_STORES
             + FS + TRUST_STORE_FILE;
 
+    // Need an enhancement to use none-static mutable global variables.
     private static ByteBuffer net;
-    private static ByteBuffer netReplicatedClient;
-    private static ByteBuffer netReplicatedServer;
+    private static boolean doUnwrapForNotHandshakingStatus;
+    private static boolean endHandshakeLoop = false;
+
     private static final int MAX_HANDSHAKE_LOOPS = 100;
     private static final String EXCHANGE_MSG_SENT = "Hello, peer!";
-    private static boolean doUnwrapForNotHandshakingStatus;
-    private static boolean endHandshakeLoop = false;
     private static final String TEST_SRC = System.getProperty("test.src", ".");
     private static final String KTAB_FILENAME = "krb5.keytab.data";
     private static final String KRB_REALM = "TEST.REALM";
@@ -179,11 +183,13 @@
             List<String> supportedCiphersList = new LinkedList<>();
             for (String cipher : allSupportedCiphers) {
                 if (!cipher.contains("KRB5")
-                        && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) {
+                    && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) {
+
                     supportedCiphersList.add(cipher);
                 }
             }
-            SUPPORTED_NON_KRB_CIPHERS = supportedCiphersList.toArray(new String[0]);
+            SUPPORTED_NON_KRB_CIPHERS =
+                    supportedCiphersList.toArray(new String[0]);
         } catch (Exception ex) {
             throw new Error("Unexpected issue", ex);
         }
@@ -220,7 +226,7 @@
             List<String> supportedCiphersList = new LinkedList<>();
             for (String cipher : allSupportedCiphers) {
                 if (cipher.contains("KRB5")
-                        && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) {
+                    && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) {
                     supportedCiphersList.add(cipher);
                 }
             }
@@ -240,11 +246,12 @@
             List<String> enabledCiphersList = new LinkedList<>();
             for (String cipher : enabledCiphers) {
                 if (!cipher.contains("anon") && !cipher.contains("KRB5")
-                        && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) {
+                    && !cipher.contains("TLS_EMPTY_RENEGOTIATION_INFO_SCSV")) {
                     enabledCiphersList.add(cipher);
                 }
             }
-            ENABLED_NON_KRB_NOT_ANON_CIPHERS = enabledCiphersList.toArray(new String[0]);
+            ENABLED_NON_KRB_NOT_ANON_CIPHERS =
+                    enabledCiphersList.toArray(new String[0]);
         } catch (Exception ex) {
             throw new Error("Unexpected issue", ex);
         }
@@ -300,10 +307,10 @@
      * Wraps data with the specified engine.
      *
      * @param engine        - SSLEngine that wraps data.
-     * @param wrapper       - Set wrapper id, e.g. "server" of "client". Used for
-     *                      logging only.
-     * @param maxPacketSize - Max packet size to check that MFLN extension works
-     *                      or zero for no check.
+     * @param wrapper       - Set wrapper id, e.g. "server" of "client".
+     *                        Used for logging only.
+     * @param maxPacketSize - Max packet size to check that MFLN extension
+     *                        works or zero for no check.
      * @param app           - Buffer with data to wrap.
      * @return - Buffer with wrapped data.
      * @throws SSLException - thrown on engine errors.
@@ -319,13 +326,13 @@
      * Wraps data with the specified engine.
      *
      * @param engine        - SSLEngine that wraps data.
-     * @param wrapper       - Set wrapper id, e.g. "server" of "client". Used for
-     *                      logging only.
-     * @param maxPacketSize - Max packet size to check that MFLN extension works
-     *                      or zero for no check.
+     * @param wrapper       - Set wrapper id, e.g. "server" of "client".
+     *                        Used for logging only.
+     * @param maxPacketSize - Max packet size to check that MFLN extension
+     *                        works or zero for no check.
      * @param app           - Buffer with data to wrap.
-     * @param result        - Array which first element will be used to output wrap
-     *                      result object.
+     * @param result        - Array which first element will be used to
+     *                        output wrap result object.
      * @return - Buffer with wrapped data.
      * @throws SSLException - thrown on engine errors.
      */
@@ -341,10 +348,10 @@
      * Wraps data with the specified engine.
      *
      * @param engine        - SSLEngine that wraps data.
-     * @param wrapper       - Set wrapper id, e.g. "server" of "client". Used for
-     *                      logging only.
-     * @param maxPacketSize - Max packet size to check that MFLN extension works
-     *                      or zero for no check.
+     * @param wrapper       - Set wrapper id, e.g. "server" of "client".
+     *                        Used for logging only.
+     * @param maxPacketSize - Max packet size to check that MFLN extension
+     *                        works or zero for no check.
      * @param app           - Buffer with data to wrap.
      * @param wantedStatus  - Specifies expected result status of wrapping.
      * @return - Buffer with wrapped data.
@@ -362,14 +369,14 @@
      * Wraps data with the specified engine.
      *
      * @param engine        - SSLEngine that wraps data.
-     * @param wrapper       - Set wrapper id, e.g. "server" of "client". Used for
-     *                      logging only.
-     * @param maxPacketSize - Max packet size to check that MFLN extension works
-     *                      or zero for no check.
+     * @param wrapper       - Set wrapper id, e.g. "server" of "client".
+     *                        Used for logging only.
+     * @param maxPacketSize - Max packet size to check that MFLN extension
+     *                        works or zero for no check.
      * @param app           - Buffer with data to wrap.
      * @param wantedStatus  - Specifies expected result status of wrapping.
-     * @param result        - Array which first element will be used to output wrap
-     *                      result object.
+     * @param result        - Array which first element will be used to output
+     *                        wrap result object.
      * @return - Buffer with wrapped data.
      * @throws SSLException - thrown on engine errors.
      */
@@ -409,9 +416,9 @@
      * @throws SSLException - thrown on engine errors.
      */
     public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper,
-                                      ByteBuffer net)
-            throws SSLException {
-        return doUnWrap(engine, unwrapper, net, SSLEngineResult.Status.OK, null);
+            ByteBuffer net) throws SSLException {
+        return doUnWrap(engine, unwrapper,
+                net, SSLEngineResult.Status.OK, null);
     }
 
     /**
@@ -427,26 +434,25 @@
      * @throws SSLException - thrown on engine errors.
      */
     public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper,
-                                      ByteBuffer net, SSLEngineResult[] result)
-            throws SSLException {
-        return doUnWrap(engine, unwrapper, net, SSLEngineResult.Status.OK, result);
+            ByteBuffer net, SSLEngineResult[] result) throws SSLException {
+        return doUnWrap(engine, unwrapper,
+                net, SSLEngineResult.Status.OK, result);
     }
 
     /**
      * Unwraps data with the specified engine.
      *
      * @param engine       - SSLEngine that unwraps data.
-     * @param unwrapper    - Set unwrapper id, e.g. "server" of "client". Used for
-     *                     logging only.
+     * @param unwrapper    - Set unwrapper id, e.g. "server" of "client".
+     *                     Used for logging only.
      * @param net          - Buffer with data to unwrap.
      * @param wantedStatus - Specifies expected result status of wrapping.
      * @return - Buffer with unwrapped data.
      * @throws SSLException - thrown on engine errors.
      */
     public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper,
-                                      ByteBuffer net,
-                                      SSLEngineResult.Status wantedStatus)
-            throws SSLException {
+            ByteBuffer net,
+            SSLEngineResult.Status wantedStatus) throws SSLException {
         return doUnWrap(engine, unwrapper, net, wantedStatus, null);
     }
 
@@ -454,25 +460,23 @@
      * Unwraps data with the specified engine.
      *
      * @param engine       - SSLEngine that unwraps data.
-     * @param unwrapper    - Set unwrapper id, e.g. "server" of "client". Used for
-     *                     logging only.
+     * @param unwrapper    - Set unwrapper id, e.g. "server" of "client".
+     *                       Used for logging only.
      * @param net          - Buffer with data to unwrap.
      * @param wantedStatus - Specifies expected result status of wrapping.
-     * @param result       - Array which first element will be used to output wrap
-     *                     result object.
+     * @param result       - Array which first element will be used to output
+     *                       wrap result object.
      * @return - Buffer with unwrapped data.
      * @throws SSLException - thrown on engine errors.
      */
     public static ByteBuffer doUnWrap(SSLEngine engine, String unwrapper,
-                                      ByteBuffer net,
-                                      SSLEngineResult.Status wantedStatus,
-                                      SSLEngineResult[] result)
-            throws SSLException {
-        ByteBuffer app = ByteBuffer.allocate(engine.getSession()
-                .getApplicationBufferSize());
+            ByteBuffer net, SSLEngineResult.Status wantedStatus,
+            SSLEngineResult[] result) throws SSLException {
+
+        ByteBuffer app = ByteBuffer.allocate(
+                engine.getSession().getApplicationBufferSize());
         int length = net.remaining();
-        System.out.println(unwrapper + " unwrapping "
-                + length + " bytes...");
+        System.out.println(unwrapper + " unwrapping " + length + " bytes...");
         SSLEngineResult r = engine.unwrap(net, app);
         app.flip();
         System.out.println(unwrapper + " handshake status is "
@@ -491,13 +495,14 @@
      * @param clientEngine  - Client SSLEngine.
      * @param serverEngine  - Server SSLEngine.
      * @param maxPacketSize - Maximum packet size for MFLN of zero for no limit.
-     * @param mode          - Handshake mode according to {@link HandshakeMode} enum.
+     * @param mode          - Handshake mode according to
+     *                        {@link HandshakeMode} enum.
      * @throws SSLException - thrown on engine errors.
      */
     public static void doHandshake(SSLEngine clientEngine,
-                                   SSLEngine serverEngine,
-                                   int maxPacketSize, HandshakeMode mode)
-            throws SSLException {
+        SSLEngine serverEngine,
+        int maxPacketSize, HandshakeMode mode) throws SSLException {
+
         doHandshake(clientEngine, serverEngine, maxPacketSize, mode, false);
     }
 
@@ -507,19 +512,20 @@
      *
      * @param clientEngine          - Client SSLEngine.
      * @param serverEngine          - Server SSLEngine.
-     * @param maxPacketSize         - Maximum packet size for MFLN of zero for no limit.
-     * @param mode                  - Handshake mode according to {@link HandshakeMode} enum.
+     * @param maxPacketSize         - Maximum packet size for MFLN of zero
+     *                                for no limit.
+     * @param mode                  - Handshake mode according to
+     *                                {@link HandshakeMode} enum.
      * @param enableReplicatedPacks - Set {@code true} to enable replicated
-     *                              packet sending.
+     *                                packet sending.
      * @throws SSLException - thrown on engine errors.
      */
     public static void doHandshake(SSLEngine clientEngine,
-                                   SSLEngine serverEngine, int maxPacketSize,
-                                   HandshakeMode mode,
-                                   boolean enableReplicatedPacks)
-            throws SSLException {
-        System.out.println("================================================="
-                + "===========");
+            SSLEngine serverEngine, int maxPacketSize,
+            HandshakeMode mode,
+            boolean enableReplicatedPacks) throws SSLException {
+
+        System.out.println("=============================================");
         System.out.println("Starting handshake " + mode.name());
         int loop = 0;
         if (maxPacketSize < 0) {
@@ -561,18 +567,16 @@
             if (++loop > MAX_HANDSHAKE_LOOPS) {
                 throw new Error("Too much loops for handshaking");
             }
-            System.out.println("==============================================");
-            System.out.println("Handshake loop " + loop);
-            SSLEngineResult.HandshakeStatus clientHSStatus
-                    = clientEngine.getHandshakeStatus();
-            SSLEngineResult.HandshakeStatus serverHSStatus
-                    = serverEngine.getHandshakeStatus();
-            System.out.println("Client handshake status "
-                    + clientHSStatus.name());
-            System.out.println("Server handshake status "
-                    + serverHSStatus.name());
+            System.out.println("============================================");
+            System.out.println("Handshake loop " + loop + ": round 1");
+            System.out.println("==========================");
             handshakeProcess(firstEngine, secondEngine, maxPacketSize,
                     enableReplicatedPacks);
+            if (endHandshakeLoop) {
+                break;
+            }
+            System.out.println("Handshake loop " + loop + ": round 2");
+            System.out.println("==========================");
             handshakeProcess(secondEngine, firstEngine, maxPacketSize,
                     enableReplicatedPacks);
         }
@@ -596,15 +600,15 @@
             sender = "Client";
             reciever = "Server";
             excMsgSent += " Client.";
-        } else if (toEngine.getUseClientMode() && !fromEngine.getUseClientMode()) {
+        } else if (toEngine.getUseClientMode() &&
+                !fromEngine.getUseClientMode()) {
             sender = "Server";
             reciever = "Client";
             excMsgSent += " Server.";
         } else {
             throw new Error("Test issue: both engines are in the same mode");
         }
-        System.out.println("================================================="
-                + "===========");
+        System.out.println("=============================================");
         System.out.println("Trying to send application data from " + sender
                 + " to " + reciever);
         ByteBuffer clientAppSent
@@ -643,20 +647,24 @@
         if (fromEngine.getUseClientMode() && !toEngine.getUseClientMode()) {
             from = "Client";
             to = "Server";
-        } else if (toEngine.getUseClientMode() && !fromEngine.getUseClientMode()) {
+        } else if (toEngine.getUseClientMode() &&
+                !fromEngine.getUseClientMode()) {
             from = "Server";
             to = "Client";
         } else {
             throw new Error("Both engines are in the same mode");
         }
-        System.out.println("=========================================================");
-        System.out.println("Trying to close engines from " + from + " to " + to);
+        System.out.println("=============================================");
+        System.out.println(
+                "Trying to close engines from " + from + " to " + to);
         // Sending close outbound request to peer
         fromEngine.closeOutbound();
-        app = ByteBuffer.allocate(fromEngine.getSession().getApplicationBufferSize());
+        app = ByteBuffer.allocate(
+                fromEngine.getSession().getApplicationBufferSize());
         net = doWrap(fromEngine, from, 0, app, SSLEngineResult.Status.CLOSED);
         doUnWrap(toEngine, to, net, SSLEngineResult.Status.CLOSED);
-        app = ByteBuffer.allocate(fromEngine.getSession().getApplicationBufferSize());
+        app = ByteBuffer.allocate(
+                fromEngine.getSession().getApplicationBufferSize());
         net = doWrap(toEngine, to, 0, app, SSLEngineResult.Status.CLOSED);
         doUnWrap(fromEngine, from, net, SSLEngineResult.Status.CLOSED);
         if (!toEngine.isInboundDone()) {
@@ -665,7 +673,8 @@
         }
         // Executing close inbound
         fromEngine.closeInbound();
-        app = ByteBuffer.allocate(fromEngine.getSession().getApplicationBufferSize());
+        app = ByteBuffer.allocate(
+                fromEngine.getSession().getApplicationBufferSize());
         net = doWrap(fromEngine, from, 0, app, SSLEngineResult.Status.CLOSED);
         doUnWrap(toEngine, to, net, SSLEngineResult.Status.CLOSED);
         if (!toEngine.isOutboundDone()) {
@@ -712,7 +721,8 @@
                 runTests(Ciphers.SUPPORTED_KRB_CIPHERS);
                 break;
             default:
-                throw new Error("Test error: unexpected test mode: " + TEST_MODE);
+                throw new Error(
+                        "Test error: unexpected test mode: " + TEST_MODE);
         }
     }
 
@@ -743,28 +753,36 @@
     }
 
     /**
-     * Returns SSLContext with TESTED_SECURITY_PROTOCOL protocol and sets up keys.
+     * Returns SSLContext with TESTED_SECURITY_PROTOCOL protocol and
+     * sets up keys.
      *
-     * @return - SSLContext with a protocol specified by TESTED_SECURITY_PROTOCOL.
+     * @return - SSLContext with a protocol specified by
+     *           TESTED_SECURITY_PROTOCOL.
      */
     public static SSLContext getContext() {
         try {
-            java.security.Security.setProperty("jdk.tls.disabledAlgorithms", "");
-            java.security.Security.setProperty("jdk.certpath.disabledAlgorithms", "");
+            java.security.Security.setProperty(
+                    "jdk.tls.disabledAlgorithms", "");
+            java.security.Security.setProperty(
+                    "jdk.certpath.disabledAlgorithms", "");
             KeyStore ks = KeyStore.getInstance("JKS");
             KeyStore ts = KeyStore.getInstance("JKS");
             char[] passphrase = PASSWD.toCharArray();
-            try (FileInputStream keyFileStream = new FileInputStream(KEY_FILE_NAME)) {
+            try (FileInputStream keyFileStream =
+                    new FileInputStream(KEY_FILE_NAME)) {
                 ks.load(keyFileStream, passphrase);
             }
-            try (FileInputStream trustFileStream = new FileInputStream(TRUST_FILE_NAME)) {
+            try (FileInputStream trustFileStream =
+                    new FileInputStream(TRUST_FILE_NAME)) {
                 ts.load(trustFileStream, passphrase);
             }
             KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
             kmf.init(ks, passphrase);
-            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+            TrustManagerFactory tmf =
+                    TrustManagerFactory.getInstance("SunX509");
             tmf.init(ts);
-            SSLContext sslCtx = SSLContext.getInstance(TESTED_SECURITY_PROTOCOL);
+            SSLContext sslCtx =
+                    SSLContext.getInstance(TESTED_SECURITY_PROTOCOL);
             sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
             return sslCtx;
         } catch (KeyStoreException | IOException | NoSuchAlgorithmException |
@@ -791,7 +809,8 @@
     }
 
     /**
-     * Sets up and starts kerberos KDC server if SSLEngineTestCase.TEST_MODE is "krb".
+     * Sets up and starts kerberos KDC server if
+     * SSLEngineTestCase.TEST_MODE is "krb".
      */
     public static void setUpAndStartKDCIfNeeded() {
         if (TEST_MODE.equals("krb")) {
@@ -806,7 +825,9 @@
      * @param useSNI  - flag used to enable or disable using SNI extension.
      *                Needed for Kerberos.
      */
-    public static SSLEngine getClientSSLEngine(SSLContext context, boolean useSNI) {
+    public static SSLEngine getClientSSLEngine(
+            SSLContext context, boolean useSNI) {
+
         SSLEngine clientEngine = context.createSSLEngine(HOST, 80);
         clientEngine.setUseClientMode(true);
         if (useSNI) {
@@ -827,7 +848,9 @@
      * @param useSNI  - flag used to enable or disable using SNI extension.
      *                Needed for Kerberos.
      */
-    public static SSLEngine getServerSSLEngine(SSLContext context, boolean useSNI) {
+    public static SSLEngine getServerSSLEngine(
+            SSLContext context, boolean useSNI) {
+
         SSLEngine serverEngine = context.createSSLEngine();
         serverEngine.setUseClientMode(false);
         if (useSNI) {
@@ -860,18 +883,20 @@
     protected int testSomeCiphers(Ciphers ciphers) {
         int failedNum = 0;
         String description = ciphers.description;
-        System.out.println("==================================================="
-                + "=========");
+        System.out.println("===============================================");
         System.out.println(description + " ciphers testing");
-        System.out.println("==================================================="
-                + "=========");
+        System.out.println("===========================================");
         for (String cs : ciphers.ciphers) {
-            System.out.println("-----------------------------------------------"
-                    + "-------------");
+            System.out.println("---------------------------------------");
             System.out.println("Testing cipher suite " + cs);
-            System.out.println("-----------------------------------------------"
-                    + "-------------");
+            System.out.println("---------------------------------------");
             Throwable error = null;
+
+            // Reset global mutable static variables
+            net = null;
+            doUnwrapForNotHandshakingStatus = false;
+            endHandshakeLoop = false;
+
             try {
                 testOneCipher(cs);
             } catch (Throwable t) {
@@ -894,8 +919,9 @@
                 case UNSUPPORTED_CIPHERS:
                     if (error == null) {
                         System.out.println("Test Failed: " + cs);
-                        System.err.println("Test for " + cs + " should have thrown"
-                                + " IllegalArgumentException, but it has not!");
+                        System.err.println("Test for " + cs +
+                                " should have thrown " +
+                                "IllegalArgumentException, but it has not!");
                         failedNum++;
                     } else if (!(error instanceof IllegalArgumentException)) {
                         System.out.println("Test Failed: " + cs);
@@ -911,6 +937,7 @@
                             + ciphers.name());
             }
         }
+
         return failedNum;
     }
 
@@ -919,20 +946,20 @@
      *
      * @param wrapingEngine         - Engine that is expected to wrap data.
      * @param unwrapingEngine       - Engine that is expected to unwrap data.
-     * @param maxPacketSize         - Maximum packet size for MFLN of zero for no limit.
+     * @param maxPacketSize         - Maximum packet size for MFLN of zero
+     *                                for no limit.
      * @param enableReplicatedPacks - Set {@code true} to enable replicated
-     *                              packet sending.
+     *                                packet sending.
      * @throws SSLException - thrown on engine errors.
      */
     private static void handshakeProcess(SSLEngine wrapingEngine,
-                                         SSLEngine unwrapingEngine,
-                                         int maxPacketSize,
-                                         boolean enableReplicatedPacks)
-            throws SSLException {
-        SSLEngineResult.HandshakeStatus wrapingHSStatus = wrapingEngine
-                .getHandshakeStatus();
-        SSLEngineResult.HandshakeStatus unwrapingHSStatus = unwrapingEngine
-                .getHandshakeStatus();
+            SSLEngine unwrapingEngine,
+            int maxPacketSize,
+            boolean enableReplicatedPacks) throws SSLException {
+
+        HandshakeStatus wrapingHSStatus = wrapingEngine.getHandshakeStatus();
+        HandshakeStatus unwrapingHSStatus =
+                unwrapingEngine.getHandshakeStatus();
         SSLEngineResult r;
         String wrapper, unwrapper;
         if (wrapingEngine.getUseClientMode()
@@ -946,6 +973,13 @@
         } else {
             throw new Error("Both engines are in the same mode");
         }
+        System.out.println(
+                wrapper + " handshake (wrap) status " + wrapingHSStatus);
+        System.out.println(
+                unwrapper + " handshake (unwrap) status " + unwrapingHSStatus);
+
+        ByteBuffer netReplicatedClient = null;
+        ByteBuffer netReplicatedServer = null;
         switch (wrapingHSStatus) {
             case NEED_WRAP:
                 if (enableReplicatedPacks) {
@@ -960,9 +994,11 @@
                         }
                     }
                 }
-                ByteBuffer app = ByteBuffer.allocate(wrapingEngine.getSession()
-                        .getApplicationBufferSize());
+                ByteBuffer app = ByteBuffer.allocate(
+                        wrapingEngine.getSession().getApplicationBufferSize());
                 net = doWrap(wrapingEngine, wrapper, maxPacketSize, app);
+                wrapingHSStatus = wrapingEngine.getHandshakeStatus();
+                // No break, falling into unwrapping.
             case NOT_HANDSHAKING:
                 switch (unwrapingHSStatus) {
                     case NEED_TASK:
@@ -970,12 +1006,12 @@
                     case NEED_UNWRAP:
                         doUnWrap(unwrapingEngine, unwrapper, net);
                         if (enableReplicatedPacks) {
-                            System.out.println("Unwrapping replicated packet...");
+                            System.out.println(unwrapper +
+                                    " unwrapping replicated packet...");
                             if (unwrapingEngine.getHandshakeStatus()
-                                    .equals(SSLEngineResult.HandshakeStatus.NEED_TASK)) {
+                                    .equals(HandshakeStatus.NEED_TASK)) {
                                 runDelegatedTasks(unwrapingEngine);
                             }
-                            runDelegatedTasks(unwrapingEngine);
                             ByteBuffer netReplicated;
                             if (unwrapingEngine.getUseClientMode()) {
                                 netReplicated = netReplicatedClient;
@@ -983,7 +1019,8 @@
                                 netReplicated = netReplicatedServer;
                             }
                             if (netReplicated != null) {
-                                doUnWrap(unwrapingEngine, unwrapper, netReplicated);
+                                doUnWrap(unwrapingEngine,
+                                        unwrapper, netReplicated);
                             } else {
                                 net.flip();
                                 doUnWrap(unwrapingEngine, unwrapper, net);
@@ -994,15 +1031,39 @@
                         break;
                     case NOT_HANDSHAKING:
                         if (doUnwrapForNotHandshakingStatus) {
+                            System.out.println("Not handshake status unwrap");
                             doUnWrap(unwrapingEngine, unwrapper, net);
                             doUnwrapForNotHandshakingStatus = false;
                             break;
                         } else {
+                            if (wrapingHSStatus ==
+                                        HandshakeStatus.NOT_HANDSHAKING) {
+                                System.out.println("Handshake is completed");
+                                endHandshakeLoop = true;
+                            }
+                        }
+                        break;
+                    case NEED_WRAP:
+                        SSLSession session = unwrapingEngine.getSession();
+                        int bufferSize = session.getApplicationBufferSize();
+                        ByteBuffer b = ByteBuffer.allocate(bufferSize);
+                        net = doWrap(unwrapingEngine,
+                                        unwrapper, maxPacketSize, b);
+                        unwrapingHSStatus =
+                                unwrapingEngine.getHandshakeStatus();
+                        if ((wrapingHSStatus ==
+                                    HandshakeStatus.NOT_HANDSHAKING) &&
+                            (unwrapingHSStatus ==
+                                    HandshakeStatus.NOT_HANDSHAKING)) {
+
+                            System.out.println("Handshake is completed");
                             endHandshakeLoop = true;
                         }
+
                         break;
                     default:
-                        throw new Error("Unexpected unwraping engine handshake status "
+                        throw new Error(
+                                "Unexpected unwraping engine handshake status "
                                 + unwrapingHSStatus.name());
                 }
                 break;
@@ -1027,8 +1088,8 @@
         while ((runnable = engine.getDelegatedTask()) != null) {
             runnable.run();
         }
-        SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
-        if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
+        HandshakeStatus hs = engine.getHandshakeStatus();
+        if (hs == HandshakeStatus.NEED_TASK) {
             throw new Error("Handshake shouldn't need additional tasks.");
         }
     }