1 /*
   2  * Copyright (c) 2015, 2016, 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  * @run main/othervm DTLSOverDatagram
  33  */
  34 
  35 import java.io.*;
  36 import java.nio.*;
  37 import java.net.*;
  38 import java.util.*;
  39 import java.security.*;
  40 import java.security.cert.*;
  41 import javax.net.ssl.*;
  42 import java.util.concurrent.*;
  43 
  44 import sun.security.util.HexDumpEncoder;
  45 
  46 /**
  47  * An example to show the way to use SSLEngine in datagram connections.
  48  */
  49 public class DTLSOverDatagram {
  50 
  51     static {
  52         System.setProperty("javax.net.debug", "ssl");
  53     }
  54 
  55     private static int MAX_HANDSHAKE_LOOPS = 200;
  56     private static int MAX_APP_READ_LOOPS = 60;
  57     private static int SOCKET_TIMEOUT = 10 * 1000; // in millis
  58     private static int BUFFER_SIZE = 1024;
  59     private static int MAXIMUM_PACKET_SIZE = 1024;
  60 
  61     /*
  62      * The following is to set up the keystores.
  63      */
  64     private static String pathToStores = "../etc";
  65     private static String keyStoreFile = "keystore";
  66     private static String trustStoreFile = "truststore";
  67     private static String passwd = "passphrase";
  68 
  69     private static String keyFilename =
  70             System.getProperty("test.src", ".") + "/" + pathToStores +
  71                 "/" + keyStoreFile;
  72     private static String trustFilename =
  73             System.getProperty("test.src", ".") + "/" + pathToStores +
  74                 "/" + trustStoreFile;
  75     private static Exception clientException = null;
  76     private static Exception serverException = null;
  77 
  78     private static ByteBuffer serverApp =
  79                 ByteBuffer.wrap("Hi Client, I'm Server".getBytes());
  80     private static ByteBuffer clientApp =
  81                 ByteBuffer.wrap("Hi Server, I'm Client".getBytes());
  82 
  83     /*
  84      * =============================================================
  85      * The test case
  86      */
  87     public static void main(String[] args) throws Exception {
  88         DTLSOverDatagram testCase = new DTLSOverDatagram();
  89         testCase.runTest(testCase);
  90     }
  91 
  92     /*
  93      * Define the server side of the test.
  94      */
  95     void doServerSide(DatagramSocket socket, InetSocketAddress clientSocketAddr)
  96             throws Exception {
  97 
  98         // create SSLEngine
  99         SSLEngine engine = createSSLEngine(false);
 100 
 101         // handshaking
 102         handshake(engine, socket, clientSocketAddr, "Server");
 103 
 104         // read client application data
 105         receiveAppData(engine, socket, clientApp);
 106 
 107         // write server application data
 108         deliverAppData(engine, socket, serverApp, clientSocketAddr);
 109     }
 110 
 111     /*
 112      * Define the client side of the test.
 113      */
 114     void doClientSide(DatagramSocket socket, InetSocketAddress serverSocketAddr)
 115             throws Exception {
 116 
 117         // create SSLEngine
 118         SSLEngine engine = createSSLEngine(true);
 119 
 120         // handshaking
 121         handshake(engine, socket, serverSocketAddr, "Client");
 122 
 123         // write client application data
 124         deliverAppData(engine, socket, clientApp, serverSocketAddr);
 125 
 126         // read server application data
 127         receiveAppData(engine, socket, serverApp);
 128     }
 129 
 130     /*
 131      * =============================================================
 132      * The remainder is support stuff for DTLS operations.
 133      */
 134     SSLEngine createSSLEngine(boolean isClient) throws Exception {
 135         SSLContext context = getDTLSContext();
 136         SSLEngine engine = context.createSSLEngine();
 137 
 138         SSLParameters paras = engine.getSSLParameters();
 139         paras.setMaximumPacketSize(MAXIMUM_PACKET_SIZE);
 140 
 141         engine.setUseClientMode(isClient);
 142         engine.setSSLParameters(paras);
 143 
 144         return engine;
 145     }
 146 
 147     // handshake
 148     void handshake(SSLEngine engine, DatagramSocket socket,
 149             SocketAddress peerAddr, String side) throws Exception {
 150 
 151         boolean endLoops = false;
 152         int loops = MAX_HANDSHAKE_LOOPS;
 153         engine.beginHandshake();
 154         while (!endLoops &&
 155                 (serverException == null) && (clientException == null)) {
 156 
 157             if (--loops < 0) {
 158                 throw new RuntimeException(
 159                         "Too much loops to produce handshake packets");
 160             }
 161 
 162             SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
 163             if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
 164                 hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
 165 
 166                 log(side, "Receive DTLS records, handshake status is " + hs);
 167 
 168                 ByteBuffer iNet;
 169                 ByteBuffer iApp;
 170                 if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
 171                     byte[] buf = new byte[BUFFER_SIZE];
 172                     DatagramPacket packet = new DatagramPacket(buf, buf.length);
 173                     try {
 174                         socket.receive(packet);
 175                     } catch (SocketTimeoutException ste) {
 176                         log(side, "Warning: " + ste);
 177 
 178                         List<DatagramPacket> packets = new ArrayList<>();
 179                         boolean finished = onReceiveTimeout(
 180                                 engine, peerAddr, side, packets);
 181 
 182                         for (DatagramPacket p : packets) {
 183                             socket.send(p);
 184                         }
 185 
 186                         if (finished) {
 187                             log(side, "Handshake status is FINISHED "
 188                                     + "after calling onReceiveTimeout(), "
 189                                     + "finish the loop");
 190                             endLoops = true;
 191                         }
 192 
 193                         log(side, "New handshake status is "
 194                                 + engine.getHandshakeStatus());
 195 
 196                         continue;
 197                     }
 198 
 199                     iNet = ByteBuffer.wrap(buf, 0, packet.getLength());
 200                     iApp = ByteBuffer.allocate(BUFFER_SIZE);
 201                 } else {
 202                     iNet = ByteBuffer.allocate(0);
 203                     iApp = ByteBuffer.allocate(BUFFER_SIZE);
 204                 }
 205 
 206                 SSLEngineResult r = engine.unwrap(iNet, iApp);
 207                 SSLEngineResult.Status rs = r.getStatus();
 208                 hs = r.getHandshakeStatus();
 209                 if (rs == SSLEngineResult.Status.OK) {
 210                     // OK
 211                 } else if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
 212                     log(side, "BUFFER_OVERFLOW, handshake status is " + hs);
 213 
 214                     // the client maximum fragment size config does not work?
 215                     throw new Exception("Buffer overflow: " +
 216                         "incorrect client maximum fragment size");
 217                 } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
 218                     log(side, "BUFFER_UNDERFLOW, handshake status is " + hs);
 219 
 220                     // bad packet, or the client maximum fragment size
 221                     // config does not work?
 222                     if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 223                         throw new Exception("Buffer underflow: " +
 224                             "incorrect client maximum fragment size");
 225                     } // otherwise, ignore this packet
 226                 } else if (rs == SSLEngineResult.Status.CLOSED) {
 227                     throw new Exception(
 228                             "SSL engine closed, handshake status is " + hs);
 229                 } else {
 230                     throw new Exception("Can't reach here, result is " + rs);
 231                 }
 232 
 233                 if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
 234                     log(side, "Handshake status is FINISHED, finish the loop");
 235                     endLoops = true;
 236                 }
 237             } else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
 238                 List<DatagramPacket> packets = new ArrayList<>();
 239                 boolean finished = produceHandshakePackets(
 240                     engine, peerAddr, side, packets);
 241 
 242                 for (DatagramPacket p : packets) {
 243                     socket.send(p);
 244                 }
 245 
 246                 if (finished) {
 247                     log(side, "Handshake status is FINISHED "
 248                             + "after producing handshake packets, "
 249                             + "finish the loop");
 250                     endLoops = true;
 251                 }
 252             } else if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
 253                 runDelegatedTasks(engine);
 254             } else if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 255                 log(side, "Handshake status is NOT_HANDSHAKING, finish the loop");
 256                 endLoops = true;
 257             } else if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
 258                 throw new Exception(
 259                         "Unexpected status, SSLEngine.getHandshakeStatus() "
 260                                 + "shouldn't return FINISHED");
 261             } else {
 262                 throw new Exception("Can't reach here, handshake status is " + hs);
 263             }
 264         }
 265 
 266         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
 267         log(side, "Handshake finished, status is " + hs);
 268 
 269         if (engine.getHandshakeSession() != null) {
 270             throw new Exception(
 271                     "Handshake finished, but handshake session is not null");
 272         }
 273 
 274         SSLSession session = engine.getSession();
 275         if (session == null) {
 276             throw new Exception("Handshake finished, but session is null");
 277         }
 278         log(side, "Negotiated protocol is " + session.getProtocol());
 279         log(side, "Negotiated cipher suite is " + session.getCipherSuite());
 280 
 281         // handshake status should be NOT_HANDSHAKING
 282         // according to the spec, SSLEngine.getHandshakeStatus() can't return FINISHED
 283         if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 284             throw new Exception("Unexpected handshake status " + hs);
 285         }
 286     }
 287 
 288     // deliver application data
 289     void deliverAppData(SSLEngine engine, DatagramSocket socket,
 290             ByteBuffer appData, SocketAddress peerAddr) throws Exception {
 291 
 292         // Note: have not consider the packet loses
 293         List<DatagramPacket> packets =
 294                 produceApplicationPackets(engine, appData, peerAddr);
 295         appData.flip();
 296         for (DatagramPacket p : packets) {
 297             socket.send(p);
 298         }
 299     }
 300 
 301     // receive application data
 302     void receiveAppData(SSLEngine engine,
 303             DatagramSocket socket, ByteBuffer expectedApp) throws Exception {
 304 
 305         int loops = MAX_APP_READ_LOOPS;
 306         while ((serverException == null) && (clientException == null)) {
 307             if (--loops < 0) {
 308                 throw new RuntimeException(
 309                         "Too much loops to receive application data");
 310             }
 311 
 312             byte[] buf = new byte[BUFFER_SIZE];
 313             DatagramPacket packet = new DatagramPacket(buf, buf.length);
 314             socket.receive(packet);
 315             ByteBuffer netBuffer = ByteBuffer.wrap(buf, 0, packet.getLength());
 316             ByteBuffer recBuffer = ByteBuffer.allocate(BUFFER_SIZE);
 317             SSLEngineResult rs = engine.unwrap(netBuffer, recBuffer);
 318             recBuffer.flip();
 319             if (recBuffer.remaining() != 0) {
 320                 printHex("Received application data", recBuffer);
 321                 if (!recBuffer.equals(expectedApp)) {
 322                     System.out.println("Engine status is " + rs);
 323                     throw new Exception("Not the right application data");
 324                 }
 325                 break;
 326             }
 327         }
 328     }
 329 
 330     // produce handshake packets
 331     boolean produceHandshakePackets(SSLEngine engine, SocketAddress socketAddr,
 332             String side, List<DatagramPacket> packets) throws Exception {
 333 
 334         boolean endLoops = false;
 335         int loops = MAX_HANDSHAKE_LOOPS;
 336         while (!endLoops &&
 337                 (serverException == null) && (clientException == null)) {
 338 
 339             if (--loops < 0) {
 340                 throw new RuntimeException(
 341                         "Too much loops to produce handshake packets");
 342             }
 343 
 344             ByteBuffer oNet = ByteBuffer.allocate(32768);
 345             ByteBuffer oApp = ByteBuffer.allocate(0);
 346             SSLEngineResult r = engine.wrap(oApp, oNet);
 347             oNet.flip();
 348 
 349             SSLEngineResult.Status rs = r.getStatus();
 350             SSLEngineResult.HandshakeStatus hs = r.getHandshakeStatus();
 351             if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
 352                 // the client maximum fragment size config does not work?
 353                 throw new Exception("Buffer overflow: " +
 354                             "incorrect server maximum fragment size");
 355             } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
 356                 log(side, "Produce handshake packets: BUFFER_UNDERFLOW occured");
 357                 log(side, "Produce handshake packets: Handshake status: " + hs);
 358                 // bad packet, or the client maximum fragment size
 359                 // config does not work?
 360                 if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 361                     throw new Exception("Buffer underflow: " +
 362                             "incorrect server maximum fragment size");
 363                 } // otherwise, ignore this packet
 364             } else if (rs == SSLEngineResult.Status.CLOSED) {
 365                 throw new Exception("SSLEngine has closed");
 366             } else if (rs == SSLEngineResult.Status.OK) {
 367                 // OK
 368             } else {
 369                 throw new Exception("Can't reach here, result is " + rs);
 370             }
 371 
 372             // SSLEngineResult.Status.OK:
 373             if (oNet.hasRemaining()) {
 374                 byte[] ba = new byte[oNet.remaining()];
 375                 oNet.get(ba);
 376                 DatagramPacket packet = createHandshakePacket(ba, socketAddr);
 377                 packets.add(packet);
 378             }
 379 
 380             if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
 381                 log(side, "Produce handshake packets: "
 382                             + "Handshake status is FINISHED, finish the loop");
 383                 return true;
 384             }
 385 
 386             boolean endInnerLoop = false;
 387             SSLEngineResult.HandshakeStatus nhs = hs;
 388             while (!endInnerLoop) {
 389                 if (nhs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
 390                     runDelegatedTasks(engine);
 391                 } else if (nhs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
 392                     nhs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN ||
 393                     nhs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 394 
 395                     endInnerLoop = true;
 396                     endLoops = true;
 397                 } else if (nhs == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
 398                     endInnerLoop = true;
 399                 } else if (nhs == SSLEngineResult.HandshakeStatus.FINISHED) {
 400                     throw new Exception(
 401                             "Unexpected status, SSLEngine.getHandshakeStatus() "
 402                                     + "shouldn't return FINISHED");
 403                 } else {
 404                     throw new Exception("Can't reach here, handshake status is "
 405                             + nhs);
 406                 }
 407                 nhs = engine.getHandshakeStatus();
 408             }
 409         }
 410 
 411         return false;
 412     }
 413 
 414     DatagramPacket createHandshakePacket(byte[] ba, SocketAddress socketAddr) {
 415         return new DatagramPacket(ba, ba.length, socketAddr);
 416     }
 417 
 418     // produce application packets
 419     List<DatagramPacket> produceApplicationPackets(
 420             SSLEngine engine, ByteBuffer source,
 421             SocketAddress socketAddr) throws Exception {
 422 
 423         List<DatagramPacket> packets = new ArrayList<>();
 424         ByteBuffer appNet = ByteBuffer.allocate(32768);
 425         SSLEngineResult r = engine.wrap(source, appNet);
 426         appNet.flip();
 427 
 428         SSLEngineResult.Status rs = r.getStatus();
 429         if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
 430             // the client maximum fragment size config does not work?
 431             throw new Exception("Buffer overflow: " +
 432                         "incorrect server maximum fragment size");
 433         } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
 434             // unlikely
 435             throw new Exception("Buffer underflow during wraping");
 436         } else if (rs == SSLEngineResult.Status.CLOSED) {
 437                 throw new Exception("SSLEngine has closed");
 438         } else if (rs == SSLEngineResult.Status.OK) {
 439             // OK
 440         } else {
 441             throw new Exception("Can't reach here, result is " + rs);
 442         }
 443 
 444         // SSLEngineResult.Status.OK:
 445         if (appNet.hasRemaining()) {
 446             byte[] ba = new byte[appNet.remaining()];
 447             appNet.get(ba);
 448             DatagramPacket packet =
 449                     new DatagramPacket(ba, ba.length, socketAddr);
 450             packets.add(packet);
 451         }
 452 
 453         return packets;
 454     }
 455 
 456     // run delegated tasks
 457     void runDelegatedTasks(SSLEngine engine) throws Exception {
 458         Runnable runnable;
 459         while ((runnable = engine.getDelegatedTask()) != null) {
 460             runnable.run();
 461         }
 462 
 463         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
 464         if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
 465             throw new Exception("handshake shouldn't need additional tasks");
 466         }
 467     }
 468 
 469     // retransmission if timeout
 470     boolean onReceiveTimeout(SSLEngine engine, SocketAddress socketAddr,
 471             String side, List<DatagramPacket> packets) throws Exception {
 472 
 473         SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
 474         if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
 475             return false;
 476         } else {
 477             // retransmission of handshake messages
 478             return produceHandshakePackets(engine, socketAddr, side, packets);
 479         }
 480     }
 481 
 482     // get DTSL context
 483     SSLContext getDTLSContext() throws Exception {
 484         KeyStore ks = KeyStore.getInstance("JKS");
 485         KeyStore ts = KeyStore.getInstance("JKS");
 486 
 487         char[] passphrase = "passphrase".toCharArray();
 488 
 489         try (FileInputStream fis = new FileInputStream(keyFilename)) {
 490             ks.load(fis, passphrase);
 491         }
 492 
 493         try (FileInputStream fis = new FileInputStream(trustFilename)) {
 494             ts.load(fis, passphrase);
 495         }
 496 
 497         KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
 498         kmf.init(ks, passphrase);
 499 
 500         TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
 501         tmf.init(ts);
 502 
 503         SSLContext sslCtx = SSLContext.getInstance("DTLS");
 504 
 505         sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
 506 
 507         return sslCtx;
 508     }
 509 
 510 
 511     /*
 512      * =============================================================
 513      * The remainder is support stuff to kickstart the testing.
 514      */
 515 
 516     // Will the handshaking and application data exchange succeed?
 517     public boolean isGoodJob() {
 518         return true;
 519     }
 520 
 521     public final void runTest(DTLSOverDatagram testCase) throws Exception {
 522         try (DatagramSocket serverSocket = new DatagramSocket();
 523                 DatagramSocket clientSocket = new DatagramSocket()) {
 524 
 525             serverSocket.setSoTimeout(SOCKET_TIMEOUT);
 526             clientSocket.setSoTimeout(SOCKET_TIMEOUT);
 527 
 528             InetSocketAddress serverSocketAddr = new InetSocketAddress(
 529                     InetAddress.getLocalHost(), serverSocket.getLocalPort());
 530 
 531             InetSocketAddress clientSocketAddr = new InetSocketAddress(
 532                     InetAddress.getLocalHost(), clientSocket.getLocalPort());
 533 
 534             ExecutorService pool = Executors.newFixedThreadPool(2);
 535             Future<String> server, client;
 536 
 537             try {
 538                 server = pool.submit(new ServerCallable(
 539                         testCase, serverSocket, clientSocketAddr));
 540                 client = pool.submit(new ClientCallable(
 541                         testCase, clientSocket, serverSocketAddr));
 542             } finally {
 543                 pool.shutdown();
 544             }
 545 
 546             boolean failed = false;
 547 
 548             // wait for client to finish
 549             try {
 550                 System.out.println("Client finished: " + client.get());
 551             } catch (CancellationException | InterruptedException
 552                         | ExecutionException e) {
 553                 System.out.println("Exception on client side: ");
 554                 e.printStackTrace(System.out);
 555                 failed = true;
 556             }
 557 
 558             // wait for server to finish
 559             try {
 560                 System.out.println("Client finished: " + server.get());
 561             } catch (CancellationException | InterruptedException
 562                         | ExecutionException e) {
 563                 System.out.println("Exception on server side: ");
 564                 e.printStackTrace(System.out);
 565                 failed = true;
 566             }
 567 
 568             if (failed) {
 569                 throw new RuntimeException("Test failed");
 570             }
 571         }
 572     }
 573 
 574     final static class ServerCallable implements Callable<String> {
 575 
 576         private final DTLSOverDatagram testCase;
 577         private final DatagramSocket socket;
 578         private final InetSocketAddress clientSocketAddr;
 579 
 580         ServerCallable(DTLSOverDatagram testCase, DatagramSocket socket,
 581                 InetSocketAddress clientSocketAddr) {
 582 
 583             this.testCase = testCase;
 584             this.socket = socket;
 585             this.clientSocketAddr = clientSocketAddr;
 586         }
 587 
 588         @Override
 589         public String call() throws Exception {
 590             try {
 591                 testCase.doServerSide(socket, clientSocketAddr);
 592             } catch (Exception e) {
 593                 System.out.println("Exception in  ServerCallable.call():");
 594                 e.printStackTrace(System.out);
 595                 serverException = e;
 596 
 597                 if (testCase.isGoodJob()) {
 598                     throw e;
 599                 } else {
 600                     return "Well done, server!";
 601                 }
 602             }
 603 
 604             if (testCase.isGoodJob()) {
 605                 return "Well done, server!";
 606             } else {
 607                 throw new Exception("No expected exception");
 608             }
 609         }
 610     }
 611 
 612     final static class ClientCallable implements Callable<String> {
 613 
 614         private final DTLSOverDatagram testCase;
 615         private final DatagramSocket socket;
 616         private final InetSocketAddress serverSocketAddr;
 617 
 618         ClientCallable(DTLSOverDatagram testCase, DatagramSocket socket,
 619                 InetSocketAddress serverSocketAddr) {
 620 
 621             this.testCase = testCase;
 622             this.socket = socket;
 623             this.serverSocketAddr = serverSocketAddr;
 624         }
 625 
 626         @Override
 627         public String call() throws Exception {
 628             try {
 629                 testCase.doClientSide(socket, serverSocketAddr);
 630             } catch (Exception e) {
 631                 System.out.println("Exception in ClientCallable.call():");
 632                 e.printStackTrace(System.out);
 633                 clientException = e;
 634 
 635                 if (testCase.isGoodJob()) {
 636                     throw e;
 637                 } else {
 638                     return "Well done, client!";
 639                 }
 640             }
 641 
 642             if (testCase.isGoodJob()) {
 643                 return "Well done, client!";
 644             } else {
 645                 throw new Exception("No expected exception");
 646             }
 647         }
 648     }
 649 
 650     final static void printHex(String prefix, ByteBuffer bb) {
 651         HexDumpEncoder  dump = new HexDumpEncoder();
 652 
 653         synchronized (System.out) {
 654             System.out.println(prefix);
 655             try {
 656                 dump.encodeBuffer(bb.slice(), System.out);
 657             } catch (Exception e) {
 658                 // ignore
 659             }
 660             System.out.flush();
 661         }
 662     }
 663 
 664     final static void printHex(String prefix,
 665             byte[] bytes, int offset, int length) {
 666 
 667         HexDumpEncoder  dump = new HexDumpEncoder();
 668 
 669         synchronized (System.out) {
 670             System.out.println(prefix);
 671             try {
 672                 ByteBuffer bb = ByteBuffer.wrap(bytes, offset, length);
 673                 dump.encodeBuffer(bb, System.out);
 674             } catch (Exception e) {
 675                 // ignore
 676             }
 677             System.out.flush();
 678         }
 679     }
 680 
 681     static void log(String side, String message) {
 682         System.out.println(side + ": " + message);
 683     }
 684 }