1 /*
   2  * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 // SunJSSE does not support dynamic system properties, no way to re-use
  25 // system properties in samevm/agentvm mode.
  26 
  27 /*
  28  * @test
  29  * @bug 8043758
  30  * @summary Datagram Transport Layer Security (DTLS)
  31  * @modules java.base/sun.security.util
  32  * @library /test/lib
  33  * @run main/othervm DTLSOverDatagram
  34  */
  35 
  36 import java.nio.*;
  37 import java.net.*;
  38 import java.util.*;
  39 import javax.net.ssl.*;
  40 
  41 import jdk.test.lib.security.KeyStoreUtils;
  42 import jdk.test.lib.security.SSLContextBuilder;
  43 
  44 import java.util.concurrent.*;
  45 
  46 import sun.security.util.HexDumpEncoder;
  47 
  48 /**
  49  * An example to show the way to use SSLEngine in datagram connections.
  50  */
  51 public class DTLSOverDatagram {
  52 
  53     private static int MAX_HANDSHAKE_LOOPS = 200;
  54     private static int MAX_APP_READ_LOOPS = 60;
  55     private static int SOCKET_TIMEOUT = 10 * 1000; // in millis
  56     private static int BUFFER_SIZE = 1024;
  57     private static int MAXIMUM_PACKET_SIZE = 1024;
  58 
  59     /*
  60      * The following is to set up the keystores.
  61      */
  62     private static String pathToStores = "../etc";
  63     private static String keyStoreFile = "keystore";
  64     private static String trustStoreFile = "truststore";
  65 
  66     private static String keyFilename =
  67             System.getProperty("test.src", ".") + "/" + pathToStores +
  68                 "/" + keyStoreFile;
  69     private static String trustFilename =
  70             System.getProperty("test.src", ".") + "/" + pathToStores +
  71                 "/" + trustStoreFile;
  72     private static Exception clientException = null;
  73     private static Exception serverException = null;
  74 
  75     private static ByteBuffer serverApp =
  76                 ByteBuffer.wrap("Hi Client, I'm Server".getBytes());
  77     private static ByteBuffer clientApp =
  78                 ByteBuffer.wrap("Hi Server, I'm Client".getBytes());
  79 
  80     /*
  81      * =============================================================
  82      * The test case
  83      */
  84     public static void main(String[] args) throws Exception {
  85         DTLSOverDatagram testCase = new DTLSOverDatagram();
  86         testCase.runTest(testCase);
  87     }
  88 
  89     /*
  90      * Define the server side of the test.
  91      */
  92     void doServerSide(DatagramSocket socket, InetSocketAddress clientSocketAddr)
  93             throws Exception {
  94 
  95         // create SSLEngine
  96         SSLEngine engine = createSSLEngine(false);
  97 
  98         // handshaking
  99         handshake(engine, socket, clientSocketAddr, "Server");
 100 
 101         // read client application data
 102         receiveAppData(engine, socket, clientApp);
 103 
 104         // write server application data
 105         deliverAppData(engine, socket, serverApp, clientSocketAddr);
 106     }
 107 
 108     /*
 109      * Define the client side of the test.
 110      */
 111     void doClientSide(DatagramSocket socket, InetSocketAddress serverSocketAddr)
 112             throws Exception {
 113 
 114         // create SSLEngine
 115         SSLEngine engine = createSSLEngine(true);
 116 
 117         // handshaking
 118         handshake(engine, socket, serverSocketAddr, "Client");
 119 
 120         // write client application data
 121         deliverAppData(engine, socket, clientApp, serverSocketAddr);
 122 
 123         // read server application data
 124         receiveAppData(engine, socket, serverApp);
 125     }
 126 
 127     /*
 128      * =============================================================
 129      * The remainder is support stuff for DTLS operations.
 130      */
 131     SSLEngine createSSLEngine(boolean isClient) throws Exception {
 132         SSLContext context = getDTLSContext();
 133         SSLEngine engine = context.createSSLEngine();
 134 
 135         SSLParameters paras = engine.getSSLParameters();
 136         paras.setMaximumPacketSize(MAXIMUM_PACKET_SIZE);
 137 
 138         engine.setUseClientMode(isClient);
 139         engine.setSSLParameters(paras);
 140 
 141         return engine;
 142     }
 143 
 144     // handshake
 145     void handshake(SSLEngine engine, DatagramSocket socket,
 146             SocketAddress peerAddr, String side) throws Exception {
 147 
 148         boolean endLoops = false;
 149         int loops = MAX_HANDSHAKE_LOOPS;
 150         engine.beginHandshake();
 151         while (!endLoops &&
 152                 (serverException == null) && (clientException == null)) {
 153 
 154             if (--loops < 0) {
 155                 throw new RuntimeException(
 156                         "Too much loops to produce handshake packets");
 157             }
 158 
 159             SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
 160             log(side, "=======handshake(" + loops + ", " + hs + ")=======");
 161             if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
 162                 hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
 163 
 164                 log(side, "Receive DTLS records, handshake status is " + hs);
 165 
 166                 ByteBuffer iNet;
 167                 ByteBuffer iApp;
 168                 if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
 169                     byte[] buf = new byte[BUFFER_SIZE];
 170                     DatagramPacket packet = new DatagramPacket(buf, buf.length);
 171                     try {
 172                         socket.receive(packet);
 173                     } catch (SocketTimeoutException ste) {
 174                         log(side, "Warning: " + ste);
 175 
 176                         List<DatagramPacket> packets = new ArrayList<>();
 177                         boolean finished = onReceiveTimeout(
 178                                 engine, peerAddr, side, packets);
 179 
 180                         log(side, "Reproduced " + packets.size() + " packets");
 181                         for (DatagramPacket p : packets) {
 182                             printHex("Reproduced packet",
 183                                 p.getData(), p.getOffset(), p.getLength());
 184                             socket.send(p);
 185                         }
 186 
 187                         if (finished) {
 188                             log(side, "Handshake status is FINISHED "
 189                                     + "after calling onReceiveTimeout(), "
 190                                     + "finish the loop");
 191                             endLoops = true;
 192                         }
 193 
 194                         log(side, "New handshake status is "
 195                                 + engine.getHandshakeStatus());
 196 
 197                         continue;
 198                     }
 199 
 200                     iNet = ByteBuffer.wrap(buf, 0, packet.getLength());
 201                     iApp = ByteBuffer.allocate(BUFFER_SIZE);
 202                 } else {
 203                     iNet = ByteBuffer.allocate(0);
 204                     iApp = ByteBuffer.allocate(BUFFER_SIZE);
 205                 }
 206 
 207                 SSLEngineResult r = engine.unwrap(iNet, iApp);
 208                 SSLEngineResult.Status rs = r.getStatus();
 209                 hs = r.getHandshakeStatus();
 210                 if (rs == SSLEngineResult.Status.OK) {
 211                     // OK
 212                 } else if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
 213                     log(side, "BUFFER_OVERFLOW, handshake status is " + hs);
 214 
 215                     // the client maximum fragment size config does not work?
 216                     throw new Exception("Buffer overflow: " +
 217                         "incorrect client maximum fragment size");
 218                 } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
 219                     log(side, "BUFFER_UNDERFLOW, handshake status is " + hs);
 220 
 221                     // bad packet, or the client maximum fragment size
 222                     // config does not work?
 223                     if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 224                         throw new Exception("Buffer underflow: " +
 225                             "incorrect client maximum fragment size");
 226                     } // otherwise, ignore this packet
 227                 } else if (rs == SSLEngineResult.Status.CLOSED) {
 228                     throw new Exception(
 229                             "SSL engine closed, handshake status is " + hs);
 230                 } else {
 231                     throw new Exception("Can't reach here, result is " + rs);
 232                 }
 233 
 234                 if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
 235                     log(side, "Handshake status is FINISHED, finish the loop");
 236                     endLoops = true;
 237                 }
 238             } else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
 239                 List<DatagramPacket> packets = new ArrayList<>();
 240                 boolean finished = produceHandshakePackets(
 241                     engine, peerAddr, side, packets);
 242 
 243                 log(side, "Produced " + packets.size() + " packets");
 244                 for (DatagramPacket p : packets) {
 245                     socket.send(p);
 246                 }
 247 
 248                 if (finished) {
 249                     log(side, "Handshake status is FINISHED "
 250                             + "after producing handshake packets, "
 251                             + "finish the loop");
 252                     endLoops = true;
 253                 }
 254             } else if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
 255                 runDelegatedTasks(engine);
 256             } else if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 257                 log(side,
 258                     "Handshake status is NOT_HANDSHAKING, finish the loop");
 259                 endLoops = true;
 260             } else if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
 261                 throw new Exception(
 262                         "Unexpected status, SSLEngine.getHandshakeStatus() "
 263                                 + "shouldn't return FINISHED");
 264             } else {
 265                 throw new Exception(
 266                         "Can't reach here, handshake status is " + hs);
 267             }
 268         }
 269 
 270         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
 271         log(side, "Handshake finished, status is " + hs);
 272 
 273         if (engine.getHandshakeSession() != null) {
 274             throw new Exception(
 275                     "Handshake finished, but handshake session is not null");
 276         }
 277 
 278         SSLSession session = engine.getSession();
 279         if (session == null) {
 280             throw new Exception("Handshake finished, but session is null");
 281         }
 282         log(side, "Negotiated protocol is " + session.getProtocol());
 283         log(side, "Negotiated cipher suite is " + session.getCipherSuite());
 284 
 285         // handshake status should be NOT_HANDSHAKING
 286         //
 287         // According to the spec, SSLEngine.getHandshakeStatus() can't
 288         // return FINISHED.
 289         if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 290             throw new Exception("Unexpected handshake status " + hs);
 291         }
 292     }
 293 
 294     // deliver application data
 295     void deliverAppData(SSLEngine engine, DatagramSocket socket,
 296             ByteBuffer appData, SocketAddress peerAddr) throws Exception {
 297 
 298         // Note: have not consider the packet loses
 299         List<DatagramPacket> packets =
 300                 produceApplicationPackets(engine, appData, peerAddr);
 301         appData.flip();
 302         for (DatagramPacket p : packets) {
 303             socket.send(p);
 304         }
 305     }
 306 
 307     // receive application data
 308     void receiveAppData(SSLEngine engine,
 309             DatagramSocket socket, ByteBuffer expectedApp) throws Exception {
 310 
 311         int loops = MAX_APP_READ_LOOPS;
 312         while ((serverException == null) && (clientException == null)) {
 313             if (--loops < 0) {
 314                 throw new RuntimeException(
 315                         "Too much loops to receive application data");
 316             }
 317 
 318             byte[] buf = new byte[BUFFER_SIZE];
 319             DatagramPacket packet = new DatagramPacket(buf, buf.length);
 320             socket.receive(packet);
 321             ByteBuffer netBuffer = ByteBuffer.wrap(buf, 0, packet.getLength());
 322             ByteBuffer recBuffer = ByteBuffer.allocate(BUFFER_SIZE);
 323             SSLEngineResult rs = engine.unwrap(netBuffer, recBuffer);
 324             recBuffer.flip();
 325             if (recBuffer.remaining() != 0) {
 326                 printHex("Received application data", recBuffer);
 327                 if (!recBuffer.equals(expectedApp)) {
 328                     System.out.println("Engine status is " + rs);
 329                     throw new Exception("Not the right application data");
 330                 }
 331                 break;
 332             }
 333         }
 334     }
 335 
 336     // produce handshake packets
 337     boolean produceHandshakePackets(SSLEngine engine, SocketAddress socketAddr,
 338             String side, List<DatagramPacket> packets) throws Exception {
 339 
 340         boolean endLoops = false;
 341         int loops = MAX_HANDSHAKE_LOOPS / 2;
 342         while (!endLoops &&
 343                 (serverException == null) && (clientException == null)) {
 344 
 345             if (--loops < 0) {
 346                 throw new RuntimeException(
 347                         "Too much loops to produce handshake packets");
 348             }
 349 
 350             ByteBuffer oNet = ByteBuffer.allocate(32768);
 351             ByteBuffer oApp = ByteBuffer.allocate(0);
 352             SSLEngineResult r = engine.wrap(oApp, oNet);
 353             oNet.flip();
 354 
 355             SSLEngineResult.Status rs = r.getStatus();
 356             SSLEngineResult.HandshakeStatus hs = r.getHandshakeStatus();
 357             log(side, "----produce handshake packet(" +
 358                     loops + ", " + rs + ", " + hs + ")----");
 359             if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
 360                 // the client maximum fragment size config does not work?
 361                 throw new Exception("Buffer overflow: " +
 362                             "incorrect server maximum fragment size");
 363             } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
 364                 log(side,
 365                         "Produce handshake packets: BUFFER_UNDERFLOW occured");
 366                 log(side,
 367                         "Produce handshake packets: Handshake status: " + hs);
 368                 // bad packet, or the client maximum fragment size
 369                 // config does not work?
 370                 if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 371                     throw new Exception("Buffer underflow: " +
 372                             "incorrect server maximum fragment size");
 373                 } // otherwise, ignore this packet
 374             } else if (rs == SSLEngineResult.Status.CLOSED) {
 375                 throw new Exception("SSLEngine has closed");
 376             } else if (rs == SSLEngineResult.Status.OK) {
 377                 // OK
 378             } else {
 379                 throw new Exception("Can't reach here, result is " + rs);
 380             }
 381 
 382             // SSLEngineResult.Status.OK:
 383             if (oNet.hasRemaining()) {
 384                 byte[] ba = new byte[oNet.remaining()];
 385                 oNet.get(ba);
 386                 DatagramPacket packet = createHandshakePacket(ba, socketAddr);
 387                 packets.add(packet);
 388             }
 389 
 390             if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
 391                 log(side, "Produce handshake packets: "
 392                             + "Handshake status is FINISHED, finish the loop");
 393                 return true;
 394             }
 395 
 396             boolean endInnerLoop = false;
 397             SSLEngineResult.HandshakeStatus nhs = hs;
 398             while (!endInnerLoop) {
 399                 if (nhs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
 400                     runDelegatedTasks(engine);
 401                 } else if (nhs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
 402                     nhs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN ||
 403                     nhs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 404 
 405                     endInnerLoop = true;
 406                     endLoops = true;
 407                 } else if (nhs == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
 408                     endInnerLoop = true;
 409                 } else if (nhs == SSLEngineResult.HandshakeStatus.FINISHED) {
 410                     throw new Exception(
 411                             "Unexpected status, SSLEngine.getHandshakeStatus() "
 412                                     + "shouldn't return FINISHED");
 413                 } else {
 414                     throw new Exception("Can't reach here, handshake status is "
 415                             + nhs);
 416                 }
 417                 nhs = engine.getHandshakeStatus();
 418             }
 419         }
 420 
 421         return false;
 422     }
 423 
 424     DatagramPacket createHandshakePacket(byte[] ba, SocketAddress socketAddr) {
 425         return new DatagramPacket(ba, ba.length, socketAddr);
 426     }
 427 
 428     // produce application packets
 429     List<DatagramPacket> produceApplicationPackets(
 430             SSLEngine engine, ByteBuffer source,
 431             SocketAddress socketAddr) throws Exception {
 432 
 433         List<DatagramPacket> packets = new ArrayList<>();
 434         ByteBuffer appNet = ByteBuffer.allocate(32768);
 435         SSLEngineResult r = engine.wrap(source, appNet);
 436         appNet.flip();
 437 
 438         SSLEngineResult.Status rs = r.getStatus();
 439         if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
 440             // the client maximum fragment size config does not work?
 441             throw new Exception("Buffer overflow: " +
 442                         "incorrect server maximum fragment size");
 443         } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
 444             // unlikely
 445             throw new Exception("Buffer underflow during wraping");
 446         } else if (rs == SSLEngineResult.Status.CLOSED) {
 447                 throw new Exception("SSLEngine has closed");
 448         } else if (rs == SSLEngineResult.Status.OK) {
 449             // OK
 450         } else {
 451             throw new Exception("Can't reach here, result is " + rs);
 452         }
 453 
 454         // SSLEngineResult.Status.OK:
 455         if (appNet.hasRemaining()) {
 456             byte[] ba = new byte[appNet.remaining()];
 457             appNet.get(ba);
 458             DatagramPacket packet =
 459                     new DatagramPacket(ba, ba.length, socketAddr);
 460             packets.add(packet);
 461         }
 462 
 463         return packets;
 464     }
 465 
 466     // Get a datagram packet for the specified handshake type.
 467     static DatagramPacket getPacket(
 468             List<DatagramPacket> packets, byte handshakeType) {
 469         boolean matched = false;
 470         for (DatagramPacket packet : packets) {
 471             byte[] data = packet.getData();
 472             int offset = packet.getOffset();
 473             int length = packet.getLength();
 474 
 475             // Normally, this pakcet should be a handshake message
 476             // record.  However, even if the underlying platform
 477             // splits the record more, we don't really worry about
 478             // the improper packet loss because DTLS implementation
 479             // should be able to handle packet loss properly.
 480             //
 481             // See RFC 6347 for the detailed format of DTLS records.
 482             if (handshakeType == -1) {      // ChangeCipherSpec
 483                 // Is it a ChangeCipherSpec message?
 484                 matched = (length == 14) && (data[offset] == 0x14);
 485             } else if ((length >= 25) &&    // 25: handshake mini size
 486                 (data[offset] == 0x16)) {   // a handshake message
 487 
 488                 // check epoch number for initial handshake only
 489                 if (data[offset + 3] == 0x00) {     // 3,4: epoch
 490                     if (data[offset + 4] == 0x00) { // plaintext
 491                         matched =
 492                             (data[offset + 13] == handshakeType);
 493                     } else {                        // cipherext
 494                         // The 1st ciphertext is a Finished message.
 495                         //
 496                         // If it is not proposed to loss the Finished
 497                         // message, it is not necessary to check the
 498                         // following packets any mroe as a Finished
 499                         // message is the last handshake message.
 500                         matched = (handshakeType == 20);
 501                     }
 502                 }
 503             }
 504 
 505             if (matched) {
 506                 return packet;
 507             }
 508         }
 509 
 510         return null;
 511     }
 512 
 513     // run delegated tasks
 514     void runDelegatedTasks(SSLEngine engine) throws Exception {
 515         Runnable runnable;
 516         while ((runnable = engine.getDelegatedTask()) != null) {
 517             runnable.run();
 518         }
 519 
 520         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
 521         if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
 522             throw new Exception("handshake shouldn't need additional tasks");
 523         }
 524     }
 525 
 526     // retransmission if timeout
 527     boolean onReceiveTimeout(SSLEngine engine, SocketAddress socketAddr,
 528             String side, List<DatagramPacket> packets) throws Exception {
 529 
 530         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
 531         if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 532             return false;
 533         } else {
 534             // retransmission of handshake messages
 535             return produceHandshakePackets(engine, socketAddr, side, packets);
 536         }
 537     }
 538 
 539     // get DTSL context
 540     SSLContext getDTLSContext() throws Exception {
 541         String passphrase = "passphrase";
 542         return SSLContextBuilder.builder()
 543                 .trustStore(KeyStoreUtils.loadKeyStore(trustFilename, passphrase))
 544                 .keyStore(KeyStoreUtils.loadKeyStore(keyFilename, passphrase))
 545                 .kmfPassphrase(passphrase)
 546                 .protocol("DTLS")
 547                 .build();
 548     }
 549 
 550 
 551     /*
 552      * =============================================================
 553      * The remainder is support stuff to kickstart the testing.
 554      */
 555 
 556     // Will the handshaking and application data exchange succeed?
 557     public boolean isGoodJob() {
 558         return true;
 559     }
 560 
 561     public final void runTest(DTLSOverDatagram testCase) throws Exception {
 562         try (DatagramSocket serverSocket = new DatagramSocket();
 563                 DatagramSocket clientSocket = new DatagramSocket()) {
 564 
 565             serverSocket.setSoTimeout(SOCKET_TIMEOUT);
 566             clientSocket.setSoTimeout(SOCKET_TIMEOUT);
 567 
 568             InetSocketAddress serverSocketAddr = new InetSocketAddress(
 569                     InetAddress.getLocalHost(), serverSocket.getLocalPort());
 570 
 571             InetSocketAddress clientSocketAddr = new InetSocketAddress(
 572                     InetAddress.getLocalHost(), clientSocket.getLocalPort());
 573 
 574             ExecutorService pool = Executors.newFixedThreadPool(2);
 575             Future<String> server, client;
 576 
 577             try {
 578                 server = pool.submit(new ServerCallable(
 579                         testCase, serverSocket, clientSocketAddr));
 580                 client = pool.submit(new ClientCallable(
 581                         testCase, clientSocket, serverSocketAddr));
 582             } finally {
 583                 pool.shutdown();
 584             }
 585 
 586             boolean failed = false;
 587 
 588             // wait for client to finish
 589             try {
 590                 System.out.println("Client finished: " + client.get());
 591             } catch (CancellationException | InterruptedException
 592                         | ExecutionException e) {
 593                 System.out.println("Exception on client side: ");
 594                 e.printStackTrace(System.out);
 595                 failed = true;
 596             }
 597 
 598             // wait for server to finish
 599             try {
 600                 System.out.println("Client finished: " + server.get());
 601             } catch (CancellationException | InterruptedException
 602                         | ExecutionException e) {
 603                 System.out.println("Exception on server side: ");
 604                 e.printStackTrace(System.out);
 605                 failed = true;
 606             }
 607 
 608             if (failed) {
 609                 throw new RuntimeException("Test failed");
 610             }
 611         }
 612     }
 613 
 614     final static class ServerCallable implements Callable<String> {
 615 
 616         private final DTLSOverDatagram testCase;
 617         private final DatagramSocket socket;
 618         private final InetSocketAddress clientSocketAddr;
 619 
 620         ServerCallable(DTLSOverDatagram testCase, DatagramSocket socket,
 621                 InetSocketAddress clientSocketAddr) {
 622 
 623             this.testCase = testCase;
 624             this.socket = socket;
 625             this.clientSocketAddr = clientSocketAddr;
 626         }
 627 
 628         @Override
 629         public String call() throws Exception {
 630             try {
 631                 testCase.doServerSide(socket, clientSocketAddr);
 632             } catch (Exception e) {
 633                 System.out.println("Exception in  ServerCallable.call():");
 634                 e.printStackTrace(System.out);
 635                 serverException = e;
 636 
 637                 if (testCase.isGoodJob()) {
 638                     throw e;
 639                 } else {
 640                     return "Well done, server!";
 641                 }
 642             }
 643 
 644             if (testCase.isGoodJob()) {
 645                 return "Well done, server!";
 646             } else {
 647                 throw new Exception("No expected exception");
 648             }
 649         }
 650     }
 651 
 652     final static class ClientCallable implements Callable<String> {
 653 
 654         private final DTLSOverDatagram testCase;
 655         private final DatagramSocket socket;
 656         private final InetSocketAddress serverSocketAddr;
 657 
 658         ClientCallable(DTLSOverDatagram testCase, DatagramSocket socket,
 659                 InetSocketAddress serverSocketAddr) {
 660 
 661             this.testCase = testCase;
 662             this.socket = socket;
 663             this.serverSocketAddr = serverSocketAddr;
 664         }
 665 
 666         @Override
 667         public String call() throws Exception {
 668             try {
 669                 testCase.doClientSide(socket, serverSocketAddr);
 670             } catch (Exception e) {
 671                 System.out.println("Exception in ClientCallable.call():");
 672                 e.printStackTrace(System.out);
 673                 clientException = e;
 674 
 675                 if (testCase.isGoodJob()) {
 676                     throw e;
 677                 } else {
 678                     return "Well done, client!";
 679                 }
 680             }
 681 
 682             if (testCase.isGoodJob()) {
 683                 return "Well done, client!";
 684             } else {
 685                 throw new Exception("No expected exception");
 686             }
 687         }
 688     }
 689 
 690     final static void printHex(String prefix, ByteBuffer bb) {
 691         HexDumpEncoder  dump = new HexDumpEncoder();
 692 
 693         synchronized (System.out) {
 694             System.out.println(prefix);
 695             try {
 696                 dump.encodeBuffer(bb.slice(), System.out);
 697             } catch (Exception e) {
 698                 // ignore
 699             }
 700             System.out.flush();
 701         }
 702     }
 703 
 704     final static void printHex(String prefix,
 705             byte[] bytes, int offset, int length) {
 706 
 707         HexDumpEncoder  dump = new HexDumpEncoder();
 708 
 709         synchronized (System.out) {
 710             System.out.println(prefix);
 711             try {
 712                 ByteBuffer bb = ByteBuffer.wrap(bytes, offset, length);
 713                 dump.encodeBuffer(bb, System.out);
 714             } catch (Exception e) {
 715                 // ignore
 716             }
 717             System.out.flush();
 718         }
 719     }
 720 
 721     static void log(String side, String message) {
 722         System.out.println(side + ": " + message);
 723     }
 724 }