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 }