8129389: javax/net/ssl/DTLS tests fail intermittently
authorasmotrak
Fri, 20 May 2016 09:40:29 -0700
changeset 38446 91dcfdbe56be
parent 38445 0a88d86065f9
child 38447 435b76ccc9e3
8129389: javax/net/ssl/DTLS tests fail intermittently Reviewed-by: xuelei
jdk/test/javax/net/ssl/DTLS/DTLSOverDatagram.java
jdk/test/javax/net/ssl/DTLS/Reordered.java
jdk/test/javax/net/ssl/DTLS/Retransmission.java
--- a/jdk/test/javax/net/ssl/DTLS/DTLSOverDatagram.java	Fri May 20 11:41:29 2016 -0300
+++ b/jdk/test/javax/net/ssl/DTLS/DTLSOverDatagram.java	Fri May 20 09:40:29 2016 -0700
@@ -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
@@ -47,8 +47,16 @@
  * An example to show the way to use SSLEngine in datagram connections.
  */
 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
+    private static int BUFFER_SIZE = 1024;
+    private static int MAXIMUM_PACKET_SIZE = 1024;
 
     /*
      * The following is to set up the keystores.
@@ -84,37 +92,33 @@
     /*
      * Define the server side of the test.
      */
-    void doServerSide() throws Exception {
-        DatagramSocket socket = serverDatagramSocket;
-        socket.setSoTimeout(10000);   // 10 second
+    void doServerSide(DatagramSocket socket, InetSocketAddress clientSocketAddr)
+            throws Exception {
 
         // create SSLEngine
         SSLEngine engine = createSSLEngine(false);
 
         // handshaking
-        handshake(engine, socket, clientSocketAddr);
+        handshake(engine, socket, clientSocketAddr, "Server");
 
         // read client application data
         receiveAppData(engine, socket, clientApp);
 
         // write server application data
         deliverAppData(engine, socket, serverApp, clientSocketAddr);
-
-        socket.close();
     }
 
     /*
      * Define the client side of the test.
      */
-    void doClientSide() throws Exception {
-        DatagramSocket socket = clientDatagramSocket;
-        socket.setSoTimeout(1000);     // 1 second read timeout
+    void doClientSide(DatagramSocket socket, InetSocketAddress serverSocketAddr)
+            throws Exception {
 
         // create SSLEngine
         SSLEngine engine = createSSLEngine(true);
 
         // handshaking
-        handshake(engine, socket, serverSocketAddr);
+        handshake(engine, socket, serverSocketAddr, "Client");
 
         // write client application data
         deliverAppData(engine, socket, clientApp, serverSocketAddr);
@@ -132,7 +136,7 @@
         SSLEngine engine = context.createSSLEngine();
 
         SSLParameters paras = engine.getSSLParameters();
-        paras.setMaximumPacketSize(1024);
+        paras.setMaximumPacketSize(MAXIMUM_PACKET_SIZE);
 
         engine.setUseClientMode(isClient);
         engine.setSSLParameters(paras);
@@ -142,7 +146,7 @@
 
     // handshake
     void handshake(SSLEngine engine, DatagramSocket socket,
-            SocketAddress peerAddr) throws Exception {
+            SocketAddress peerAddr, String side) throws Exception {
 
         boolean endLoops = false;
         int loops = MAX_HANDSHAKE_LOOPS;
@@ -159,39 +163,60 @@
             if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
                 hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
 
+                log(side, "Receive DTLS records, handshake status is " + hs);
+
                 ByteBuffer iNet;
                 ByteBuffer iApp;
                 if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
-                    // receive ClientHello request and other SSL/TLS records
-                    byte[] buf = new byte[1024];
+                    byte[] buf = new byte[BUFFER_SIZE];
                     DatagramPacket packet = new DatagramPacket(buf, buf.length);
                     try {
                         socket.receive(packet);
                     } catch (SocketTimeoutException ste) {
-                        List<DatagramPacket> packets =
-                                onReceiveTimeout(engine, peerAddr);
+                        log(side, "Warning: " + ste);
+
+                        List<DatagramPacket> packets = new ArrayList<>();
+                        boolean finished = onReceiveTimeout(
+                                engine, peerAddr, side, packets);
+
                         for (DatagramPacket p : packets) {
                             socket.send(p);
                         }
 
+                        if (finished) {
+                            log(side, "Handshake status is FINISHED "
+                                    + "after calling onReceiveTimeout(), "
+                                    + "finish the loop");
+                            endLoops = true;
+                        }
+
+                        log(side, "New handshake status is "
+                                + engine.getHandshakeStatus());
+
                         continue;
                     }
 
                     iNet = ByteBuffer.wrap(buf, 0, packet.getLength());
-                    iApp = ByteBuffer.allocate(1024);
+                    iApp = ByteBuffer.allocate(BUFFER_SIZE);
                 } else {
                     iNet = ByteBuffer.allocate(0);
-                    iApp = ByteBuffer.allocate(1024);
+                    iApp = ByteBuffer.allocate(BUFFER_SIZE);
                 }
 
                 SSLEngineResult r = engine.unwrap(iNet, iApp);
                 SSLEngineResult.Status rs = r.getStatus();
                 hs = r.getHandshakeStatus();
-                if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
+                if (rs == SSLEngineResult.Status.OK) {
+                    // OK
+                } else if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
+                    log(side, "BUFFER_OVERFLOW, handshake status is " + hs);
+
                     // the client maximum fragment size config does not work?
                     throw new Exception("Buffer overflow: " +
                         "incorrect client maximum fragment size");
                 } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
+                    log(side, "BUFFER_UNDERFLOW, handshake status is " + hs);
+
                     // bad packet, or the client maximum fragment size
                     // config does not work?
                     if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
@@ -199,31 +224,64 @@
                             "incorrect client maximum fragment size");
                     } // otherwise, ignore this packet
                 } else if (rs == SSLEngineResult.Status.CLOSED) {
-                    endLoops = true;
-                }   // otherwise, SSLEngineResult.Status.OK:
+                    throw new Exception(
+                            "SSL engine closed, handshake status is " + hs);
+                } else {
+                    throw new Exception("Can't reach here, result is " + rs);
+                }
 
-                if (rs != SSLEngineResult.Status.OK) {
-                    continue;
+                if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
+                    log(side, "Handshake status is FINISHED, finish the loop");
+                    endLoops = true;
                 }
             } else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
-                List<DatagramPacket> packets =
-                        produceHandshakePackets(engine, peerAddr);
+                List<DatagramPacket> packets = new ArrayList<>();
+                boolean finished = produceHandshakePackets(
+                    engine, peerAddr, side, packets);
+
                 for (DatagramPacket p : packets) {
                     socket.send(p);
                 }
+
+                if (finished) {
+                    log(side, "Handshake status is FINISHED "
+                            + "after producing handshake packets, "
+                            + "finish the loop");
+                    endLoops = true;
+                }
             } else if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
                 runDelegatedTasks(engine);
             } else if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
-                // OK, time to do application data exchange.
+                log(side, "Handshake status is NOT_HANDSHAKING, finish the loop");
                 endLoops = true;
             } else if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
-                endLoops = true;
+                throw new Exception(
+                        "Unexpected status, SSLEngine.getHandshakeStatus() "
+                                + "shouldn't return FINISHED");
+            } else {
+                throw new Exception("Can't reach here, handshake status is " + hs);
             }
         }
 
         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
+        log(side, "Handshake finished, status is " + hs);
+
+        if (engine.getHandshakeSession() != null) {
+            throw new Exception(
+                    "Handshake finished, but handshake session is not null");
+        }
+
+        SSLSession session = engine.getSession();
+        if (session == null) {
+            throw new Exception("Handshake finished, but session is null");
+        }
+        log(side, "Negotiated protocol is " + session.getProtocol());
+        log(side, "Negotiated cipher suite is " + session.getCipherSuite());
+
+        // handshake status should be NOT_HANDSHAKING
+        // according to the spec, SSLEngine.getHandshakeStatus() can't return FINISHED
         if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
-            throw new Exception("Not ready for application data yet");
+            throw new Exception("Unexpected handshake status " + hs);
         }
     }
 
@@ -251,11 +309,11 @@
                         "Too much loops to receive application data");
             }
 
-            byte[] buf = new byte[1024];
+            byte[] buf = new byte[BUFFER_SIZE];
             DatagramPacket packet = new DatagramPacket(buf, buf.length);
             socket.receive(packet);
             ByteBuffer netBuffer = ByteBuffer.wrap(buf, 0, packet.getLength());
-            ByteBuffer recBuffer = ByteBuffer.allocate(1024);
+            ByteBuffer recBuffer = ByteBuffer.allocate(BUFFER_SIZE);
             SSLEngineResult rs = engine.unwrap(netBuffer, recBuffer);
             recBuffer.flip();
             if (recBuffer.remaining() != 0) {
@@ -270,10 +328,9 @@
     }
 
     // produce handshake packets
-    List<DatagramPacket> produceHandshakePackets(
-            SSLEngine engine, SocketAddress socketAddr) throws Exception {
+    boolean produceHandshakePackets(SSLEngine engine, SocketAddress socketAddr,
+            String side, List<DatagramPacket> packets) throws Exception {
 
-        List<DatagramPacket> packets = new ArrayList<>();
         boolean endLoops = false;
         int loops = MAX_HANDSHAKE_LOOPS;
         while (!endLoops &&
@@ -296,6 +353,8 @@
                 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);
                 // bad packet, or the client maximum fragment size
                 // config does not work?
                 if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
@@ -304,7 +363,11 @@
                 } // otherwise, ignore this packet
             } else if (rs == SSLEngineResult.Status.CLOSED) {
                 throw new Exception("SSLEngine has closed");
-            }   // otherwise, SSLEngineResult.Status.OK
+            } else if (rs == SSLEngineResult.Status.OK) {
+                // OK
+            } else {
+                throw new Exception("Can't reach here, result is " + rs);
+            }
 
             // SSLEngineResult.Status.OK:
             if (oNet.hasRemaining()) {
@@ -313,25 +376,39 @@
                 DatagramPacket packet = createHandshakePacket(ba, socketAddr);
                 packets.add(packet);
             }
+
+            if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
+                log(side, "Produce handshake packets: "
+                            + "Handshake status is FINISHED, finish the loop");
+                return true;
+            }
+
             boolean endInnerLoop = false;
             SSLEngineResult.HandshakeStatus nhs = hs;
             while (!endInnerLoop) {
                 if (nhs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
                     runDelegatedTasks(engine);
-                    nhs = engine.getHandshakeStatus();
-                } else if ((nhs == SSLEngineResult.HandshakeStatus.FINISHED) ||
-                    (nhs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) ||
-                    (nhs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)) {
+                } else if (nhs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
+                    nhs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN ||
+                    nhs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 
                     endInnerLoop = true;
                     endLoops = true;
                 } else if (nhs == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
                     endInnerLoop = true;
+                } else if (nhs == 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 "
+                            + nhs);
                 }
+                nhs = engine.getHandshakeStatus();
             }
         }
 
-        return packets;
+        return false;
     }
 
     DatagramPacket createHandshakePacket(byte[] ba, SocketAddress socketAddr) {
@@ -358,7 +435,11 @@
             throw new Exception("Buffer underflow during wraping");
         } else if (rs == SSLEngineResult.Status.CLOSED) {
                 throw new Exception("SSLEngine has closed");
-        }   // otherwise, SSLEngineResult.Status.OK
+        } else if (rs == SSLEngineResult.Status.OK) {
+            // OK
+        } else {
+            throw new Exception("Can't reach here, result is " + rs);
+        }
 
         // SSLEngineResult.Status.OK:
         if (appNet.hasRemaining()) {
@@ -386,15 +467,15 @@
     }
 
     // retransmission if timeout
-    List<DatagramPacket> onReceiveTimeout(
-            SSLEngine engine, SocketAddress socketAddr) throws Exception {
+    boolean onReceiveTimeout(SSLEngine engine, SocketAddress socketAddr,
+            String side, List<DatagramPacket> packets) throws Exception {
 
         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
         if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
-            return new ArrayList<DatagramPacket>();
+            return false;
         } else {
             // retransmission of handshake messages
-            return produceHandshakePackets(engine, socketAddr);
+            return produceHandshakePackets(engine, socketAddr, side, packets);
         }
     }
 
@@ -405,8 +486,13 @@
 
         char[] passphrase = "passphrase".toCharArray();
 
-        ks.load(new FileInputStream(keyFilename), passphrase);
-        ts.load(new FileInputStream(trustFilename), passphrase);
+        try (FileInputStream fis = new FileInputStream(keyFilename)) {
+            ks.load(fis, passphrase);
+        }
+
+        try (FileInputStream fis = new FileInputStream(trustFilename)) {
+            ts.load(fis, passphrase);
+        }
 
         KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
         kmf.init(ks, passphrase);
@@ -427,94 +513,84 @@
      * The remainder is support stuff to kickstart the testing.
      */
 
-    // the server side SocketAddress
-    volatile static SocketAddress serverSocketAddr = null;
-
-    // the client side SocketAddress
-    volatile static SocketAddress clientSocketAddr = null;
-
-    // the server side DatagramSocket instance
-    volatile static DatagramSocket serverDatagramSocket = null;
-
-    // the server side DatagramSocket instance
-    volatile static DatagramSocket clientDatagramSocket = null;
-
-    // get server side SocketAddress object
-    public SocketAddress getServerSocketAddress() {
-        return serverSocketAddr;
-    }
-
-    // get client side SocketAddress object
-    public SocketAddress getClientSocketAddress() {
-        return clientSocketAddr;
-    }
-
-    // get server side DatagramSocket object
-    public DatagramSocket getServerDatagramSocket() {
-        return serverDatagramSocket;
-    }
-
-    // get client side DatagramSocket object
-    public DatagramSocket getClientDatagramSocket() {
-        return clientDatagramSocket;
-    }
-
     // Will the handshaking and application data exchange succeed?
     public boolean isGoodJob() {
         return true;
     }
 
     public final void runTest(DTLSOverDatagram testCase) throws Exception {
-        serverDatagramSocket = new DatagramSocket();
-        serverSocketAddr = new InetSocketAddress(
-            InetAddress.getLocalHost(), serverDatagramSocket.getLocalPort());
+        try (DatagramSocket serverSocket = new DatagramSocket();
+                DatagramSocket clientSocket = new DatagramSocket()) {
+
+            serverSocket.setSoTimeout(SOCKET_TIMEOUT);
+            clientSocket.setSoTimeout(SOCKET_TIMEOUT);
 
-        clientDatagramSocket = new DatagramSocket();
-        clientSocketAddr = new InetSocketAddress(
-            InetAddress.getLocalHost(), clientDatagramSocket.getLocalPort());
+            InetSocketAddress serverSocketAddr = new InetSocketAddress(
+                    InetAddress.getLocalHost(), serverSocket.getLocalPort());
+
+            InetSocketAddress clientSocketAddr = new InetSocketAddress(
+                    InetAddress.getLocalHost(), clientSocket.getLocalPort());
 
-        ExecutorService pool = Executors.newFixedThreadPool(2);
-        List<Future<String>> list = new ArrayList<Future<String>>();
+            ExecutorService pool = Executors.newFixedThreadPool(2);
+            Future<String> server, client;
 
-        try {
-            list.add(pool.submit(new ServerCallable(testCase)));  // server task
-            list.add(pool.submit(new ClientCallable(testCase)));  // client task
-        } finally {
-            pool.shutdown();
-        }
+            try {
+                server = pool.submit(new ServerCallable(
+                        testCase, serverSocket, clientSocketAddr));
+                client = pool.submit(new ClientCallable(
+                        testCase, clientSocket, serverSocketAddr));
+            } finally {
+                pool.shutdown();
+            }
 
-        Exception reserved = null;
-        for (Future<String> fut : list) {
+            boolean failed = false;
+
+            // wait for client to finish
             try {
-                System.out.println(fut.get());
-            } catch (CancellationException |
-                    InterruptedException | ExecutionException cie) {
-                if (reserved != null) {
-                    cie.addSuppressed(reserved);
-                    reserved = cie;
-                } else {
-                    reserved = cie;
-                }
+                System.out.println("Client finished: " + client.get());
+            } catch (CancellationException | InterruptedException
+                        | ExecutionException e) {
+                System.out.println("Exception on client side: ");
+                e.printStackTrace(System.out);
+                failed = true;
             }
-        }
 
-        if (reserved != null) {
-            throw reserved;
+            // wait for server to finish
+            try {
+                System.out.println("Client finished: " + server.get());
+            } catch (CancellationException | InterruptedException
+                        | ExecutionException e) {
+                System.out.println("Exception on server side: ");
+                e.printStackTrace(System.out);
+                failed = true;
+            }
+
+            if (failed) {
+                throw new RuntimeException("Test failed");
+            }
         }
     }
 
     final static class ServerCallable implements Callable<String> {
-        DTLSOverDatagram testCase;
+
+        private final DTLSOverDatagram testCase;
+        private final DatagramSocket socket;
+        private final InetSocketAddress clientSocketAddr;
 
-        ServerCallable(DTLSOverDatagram testCase) {
+        ServerCallable(DTLSOverDatagram testCase, DatagramSocket socket,
+                InetSocketAddress clientSocketAddr) {
+
             this.testCase = testCase;
+            this.socket = socket;
+            this.clientSocketAddr = clientSocketAddr;
         }
 
         @Override
         public String call() throws Exception {
             try {
-                testCase.doServerSide();
+                testCase.doServerSide(socket, clientSocketAddr);
             } catch (Exception e) {
+                System.out.println("Exception in  ServerCallable.call():");
                 e.printStackTrace(System.out);
                 serverException = e;
 
@@ -523,10 +599,6 @@
                 } else {
                     return "Well done, server!";
                 }
-            } finally {
-                if (serverDatagramSocket != null) {
-                    serverDatagramSocket.close();
-                }
             }
 
             if (testCase.isGoodJob()) {
@@ -538,28 +610,33 @@
     }
 
     final static class ClientCallable implements Callable<String> {
-        DTLSOverDatagram testCase;
+
+        private final DTLSOverDatagram testCase;
+        private final DatagramSocket socket;
+        private final InetSocketAddress serverSocketAddr;
 
-        ClientCallable(DTLSOverDatagram testCase) {
+        ClientCallable(DTLSOverDatagram testCase, DatagramSocket socket,
+                InetSocketAddress serverSocketAddr) {
+
             this.testCase = testCase;
+            this.socket = socket;
+            this.serverSocketAddr = serverSocketAddr;
         }
 
         @Override
         public String call() throws Exception {
             try {
-                testCase.doClientSide();
+                testCase.doClientSide(socket, serverSocketAddr);
             } catch (Exception e) {
+                System.out.println("Exception in ClientCallable.call():");
                 e.printStackTrace(System.out);
                 clientException = e;
+
                 if (testCase.isGoodJob()) {
                     throw e;
                 } else {
                     return "Well done, client!";
                 }
-            } finally {
-                if (clientDatagramSocket != null) {
-                    clientDatagramSocket.close();
-                }
             }
 
             if (testCase.isGoodJob()) {
@@ -600,4 +677,8 @@
             System.out.flush();
         }
     }
+
+    static void log(String side, String message) {
+        System.out.println(side + ": " + message);
+    }
 }
--- a/jdk/test/javax/net/ssl/DTLS/Reordered.java	Fri May 20 11:41:29 2016 -0300
+++ b/jdk/test/javax/net/ssl/DTLS/Reordered.java	Fri May 20 09:40:29 2016 -0700
@@ -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
@@ -51,16 +51,17 @@
     }
 
     @Override
-    List<DatagramPacket> produceHandshakePackets(
-            SSLEngine engine, SocketAddress socketAddr) throws Exception {
-        List<DatagramPacket> packets =
-                super.produceHandshakePackets(engine, socketAddr);
+    boolean produceHandshakePackets(SSLEngine engine, SocketAddress socketAddr,
+            String side, List<DatagramPacket> packets) throws Exception {
+
+        boolean finished = super.produceHandshakePackets(
+                engine, socketAddr, side, packets);
 
         if (needPacketReorder && (!engine.getUseClientMode())) {
             needPacketReorder = false;
             Collections.reverse(packets);
         }
 
-        return packets;
+        return finished;
     }
 }
--- a/jdk/test/javax/net/ssl/DTLS/Retransmission.java	Fri May 20 11:41:29 2016 -0300
+++ b/jdk/test/javax/net/ssl/DTLS/Retransmission.java	Fri May 20 09:40:29 2016 -0700
@@ -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
@@ -52,13 +52,14 @@
     }
 
     @Override
-    List<DatagramPacket> produceHandshakePackets(
-            SSLEngine engine, SocketAddress socketAddr) throws Exception {
-        List<DatagramPacket> packets =
-                super.produceHandshakePackets(engine, socketAddr);
+    boolean produceHandshakePackets(SSLEngine engine, SocketAddress socketAddr,
+            String side, List<DatagramPacket> packets) throws Exception {
+
+        boolean finished = super.produceHandshakePackets(
+                engine, socketAddr, side, packets);
 
         if (!needPacketLoss || (!engine.getUseClientMode())) {
-            return packets;
+            return finished;
         }
 
         List<DatagramPacket> parts = new ArrayList<>();
@@ -75,6 +76,9 @@
             parts.add(packet);
         }
 
-        return parts;
+        packets.clear();
+        packets.addAll(parts);
+
+        return finished;
     }
 }