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