test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/AbstractSSLTubeTest.java
branchhttp-client-branch
changeset 55947 c4f314605d28
parent 55942 8d4770c22b63
child 55968 11a97b370db0
equal deleted inserted replaced
55946:cfa4f84b7fcc 55947:c4f314605d28
       
     1 /*
       
     2  * Copyright (c) 2017, 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 package jdk.incubator.http;
       
    25 
       
    26 import jdk.incubator.http.internal.common.Demand;
       
    27 import jdk.incubator.http.internal.common.FlowTube;
       
    28 import jdk.incubator.http.internal.common.SSLFlowDelegate;
       
    29 import jdk.incubator.http.internal.common.SSLTube;
       
    30 import jdk.incubator.http.internal.common.SequentialScheduler;
       
    31 import jdk.incubator.http.internal.common.Utils;
       
    32 import org.testng.annotations.Test;
       
    33 
       
    34 import javax.net.ssl.KeyManagerFactory;
       
    35 import javax.net.ssl.SSLContext;
       
    36 import javax.net.ssl.SSLEngine;
       
    37 import javax.net.ssl.SSLParameters;
       
    38 import javax.net.ssl.SSLServerSocket;
       
    39 import javax.net.ssl.SSLServerSocketFactory;
       
    40 import javax.net.ssl.SSLSocket;
       
    41 import javax.net.ssl.TrustManagerFactory;
       
    42 import java.io.BufferedOutputStream;
       
    43 import java.io.File;
       
    44 import java.io.FileInputStream;
       
    45 import java.io.IOException;
       
    46 import java.io.InputStream;
       
    47 import java.io.OutputStream;
       
    48 import java.net.Socket;
       
    49 import java.nio.ByteBuffer;
       
    50 import java.security.KeyManagementException;
       
    51 import java.security.KeyStore;
       
    52 import java.security.KeyStoreException;
       
    53 import java.security.NoSuchAlgorithmException;
       
    54 import java.security.UnrecoverableKeyException;
       
    55 import java.security.cert.CertificateException;
       
    56 import java.util.List;
       
    57 import java.util.Queue;
       
    58 import java.util.Random;
       
    59 import java.util.StringTokenizer;
       
    60 import java.util.concurrent.BlockingQueue;
       
    61 import java.util.concurrent.CompletableFuture;
       
    62 import java.util.concurrent.ConcurrentLinkedQueue;
       
    63 import java.util.concurrent.Executor;
       
    64 import java.util.concurrent.ExecutorService;
       
    65 import java.util.concurrent.Executors;
       
    66 import java.util.concurrent.Flow;
       
    67 import java.util.concurrent.ForkJoinPool;
       
    68 import java.util.concurrent.LinkedBlockingQueue;
       
    69 import java.util.concurrent.SubmissionPublisher;
       
    70 import java.util.concurrent.atomic.AtomicBoolean;
       
    71 import java.util.concurrent.atomic.AtomicInteger;
       
    72 import java.util.concurrent.atomic.AtomicLong;
       
    73 import java.util.concurrent.atomic.AtomicReference;
       
    74 import java.util.function.Consumer;
       
    75 
       
    76 public class AbstractSSLTubeTest {
       
    77 
       
    78     public static final long COUNTER = 600;
       
    79     public static final int LONGS_PER_BUF = 800;
       
    80     public static final long TOTAL_LONGS = COUNTER * LONGS_PER_BUF;
       
    81     public static final ByteBuffer SENTINEL = ByteBuffer.allocate(0);
       
    82     public static final Random rand = new Random();
       
    83 
       
    84     protected static int randomRange(int lower, int upper) {
       
    85         if (lower > upper)
       
    86             throw new IllegalArgumentException("lower > upper");
       
    87         int diff = upper - lower;
       
    88         int r = lower + rand.nextInt(diff);
       
    89         return r - (r % 8); // round down to multiple of 8 (align for longs)
       
    90     }
       
    91 
       
    92     protected static ByteBuffer getBuffer(long startingAt) {
       
    93         ByteBuffer buf = ByteBuffer.allocate(LONGS_PER_BUF * 8);
       
    94         for (int j = 0; j < LONGS_PER_BUF; j++) {
       
    95             buf.putLong(startingAt++);
       
    96         }
       
    97         buf.flip();
       
    98         return buf;
       
    99     }
       
   100 
       
   101     protected void run(FlowTube server, ExecutorService sslExecutor) throws IOException {
       
   102         FlowTube client = new SSLTube(createSSLEngine(true),
       
   103                                       sslExecutor,
       
   104                                       server);
       
   105         SubmissionPublisher<List<ByteBuffer>> p =
       
   106                 new SubmissionPublisher<>(ForkJoinPool.commonPool(),
       
   107                                           Integer.MAX_VALUE);
       
   108         FlowTube.TubePublisher begin = p::subscribe;
       
   109         CompletableFuture<Void> completion = new CompletableFuture<>();
       
   110         EndSubscriber end = new EndSubscriber(TOTAL_LONGS, completion);
       
   111         client.connectFlows(begin, end);
       
   112         /* End of wiring */
       
   113 
       
   114         long count = 0;
       
   115         System.out.printf("Submitting %d buffer arrays\n", COUNTER);
       
   116         System.out.printf("LoopCount should be %d\n", TOTAL_LONGS);
       
   117         for (long i = 0; i < COUNTER; i++) {
       
   118             ByteBuffer b = getBuffer(count);
       
   119             count += LONGS_PER_BUF;
       
   120             p.submit(List.of(b));
       
   121         }
       
   122         System.out.println("Finished submission. Waiting for loopback");
       
   123         p.close();
       
   124         try {
       
   125             completion.join();
       
   126             System.out.println("OK");
       
   127         } finally {
       
   128             sslExecutor.shutdownNow();
       
   129         }
       
   130     }
       
   131 
       
   132     protected static void sleep(long millis) {
       
   133         try {
       
   134             Thread.sleep(millis);
       
   135         } catch (InterruptedException e) {
       
   136 
       
   137         }
       
   138     }
       
   139 
       
   140     /**
       
   141      * The final subscriber which receives the decrypted looped-back data. Just
       
   142      * needs to compare the data with what was sent. The given CF is either
       
   143      * completed exceptionally with an error or normally on success.
       
   144      */
       
   145     protected static class EndSubscriber implements FlowTube.TubeSubscriber {
       
   146 
       
   147         private static final int REQUEST_WINDOW = 13;
       
   148 
       
   149         private final long nbytes;
       
   150         private final AtomicLong counter = new AtomicLong();
       
   151         private final CompletableFuture<?> completion;
       
   152         private volatile Flow.Subscription subscription;
       
   153         private long unfulfilled;
       
   154 
       
   155         EndSubscriber(long nbytes, CompletableFuture<?> completion) {
       
   156             this.nbytes = nbytes;
       
   157             this.completion = completion;
       
   158         }
       
   159 
       
   160         @Override
       
   161         public void onSubscribe(Flow.Subscription subscription) {
       
   162             this.subscription = subscription;
       
   163             unfulfilled = REQUEST_WINDOW;
       
   164             System.out.println("EndSubscriber request " + REQUEST_WINDOW);
       
   165             subscription.request(REQUEST_WINDOW);
       
   166         }
       
   167 
       
   168         public static String info(List<ByteBuffer> i) {
       
   169             StringBuilder sb = new StringBuilder();
       
   170             sb.append("size: ").append(Integer.toString(i.size()));
       
   171             int x = 0;
       
   172             for (ByteBuffer b : i)
       
   173                 x += b.remaining();
       
   174             sb.append(" bytes: ").append(x);
       
   175             return sb.toString();
       
   176         }
       
   177 
       
   178         @Override
       
   179         public void onNext(List<ByteBuffer> buffers) {
       
   180             if (--unfulfilled == (REQUEST_WINDOW / 2)) {
       
   181                 long req = REQUEST_WINDOW - unfulfilled;
       
   182                 System.out.println("EndSubscriber request " + req);
       
   183                 unfulfilled = REQUEST_WINDOW;
       
   184                 subscription.request(req);
       
   185             }
       
   186 
       
   187             long currval = counter.get();
       
   188             if (currval % 500 == 0) {
       
   189                 System.out.println("EndSubscriber: " + currval);
       
   190             }
       
   191             System.out.println("EndSubscriber onNext " + Utils.remaining(buffers));
       
   192 
       
   193             for (ByteBuffer buf : buffers) {
       
   194                 while (buf.hasRemaining()) {
       
   195                     long n = buf.getLong();
       
   196                     if (currval > (AbstractSSLTubeTest.TOTAL_LONGS - 50)) {
       
   197                         System.out.println("End: " + currval);
       
   198                     }
       
   199                     if (n != currval++) {
       
   200                         System.out.println("ERROR at " + n + " != " + (currval - 1));
       
   201                         completion.completeExceptionally(new RuntimeException("ERROR"));
       
   202                         subscription.cancel();
       
   203                         return;
       
   204                     }
       
   205                 }
       
   206             }
       
   207 
       
   208             counter.set(currval);
       
   209         }
       
   210 
       
   211         @Override
       
   212         public void onError(Throwable throwable) {
       
   213             System.out.println("EndSubscriber onError " + throwable);
       
   214             completion.completeExceptionally(throwable);
       
   215         }
       
   216 
       
   217         @Override
       
   218         public void onComplete() {
       
   219             long n = counter.get();
       
   220             if (n != nbytes) {
       
   221                 System.out.printf("nbytes=%d n=%d\n", nbytes, n);
       
   222                 completion.completeExceptionally(new RuntimeException("ERROR AT END"));
       
   223             } else {
       
   224                 System.out.println("DONE OK");
       
   225                 completion.complete(null);
       
   226             }
       
   227         }
       
   228         @Override
       
   229         public String toString() {
       
   230             return "EndSubscriber";
       
   231         }
       
   232     }
       
   233 
       
   234     protected static SSLEngine createSSLEngine(boolean client) throws IOException {
       
   235         SSLContext context = (new SimpleSSLContext()).get();
       
   236         SSLEngine engine = context.createSSLEngine();
       
   237         SSLParameters params = context.getSupportedSSLParameters();
       
   238         params.setProtocols(new String[]{"TLSv1.2"}); // TODO: This is essential. Needs to be protocol impl
       
   239         if (client) {
       
   240             params.setApplicationProtocols(new String[]{"proto1", "proto2"}); // server will choose proto2
       
   241         } else {
       
   242             params.setApplicationProtocols(new String[]{"proto2"}); // server will choose proto2
       
   243         }
       
   244         engine.setSSLParameters(params);
       
   245         engine.setUseClientMode(client);
       
   246         return engine;
       
   247     }
       
   248 
       
   249     /**
       
   250      * Creates a simple usable SSLContext for SSLSocketFactory or a HttpsServer
       
   251      * using either a given keystore or a default one in the test tree.
       
   252      *
       
   253      * Using this class with a security manager requires the following
       
   254      * permissions to be granted:
       
   255      *
       
   256      * permission "java.util.PropertyPermission" "test.src.path", "read";
       
   257      * permission java.io.FilePermission "${test.src}/../../../../lib/testlibrary/jdk/testlibrary/testkeys",
       
   258      * "read"; The exact path above depends on the location of the test.
       
   259      */
       
   260     protected static class SimpleSSLContext {
       
   261 
       
   262         private final SSLContext ssl;
       
   263 
       
   264         /**
       
   265          * Loads default keystore from SimpleSSLContext source directory
       
   266          */
       
   267         public SimpleSSLContext() throws IOException {
       
   268             String paths = System.getProperty("test.src.path");
       
   269             StringTokenizer st = new StringTokenizer(paths, File.pathSeparator);
       
   270             boolean securityExceptions = false;
       
   271             SSLContext sslContext = null;
       
   272             while (st.hasMoreTokens()) {
       
   273                 String path = st.nextToken();
       
   274                 try {
       
   275                     File f = new File(path, "../../../../lib/testlibrary/jdk/testlibrary/testkeys");
       
   276                     if (f.exists()) {
       
   277                         try (FileInputStream fis = new FileInputStream(f)) {
       
   278                             sslContext = init(fis);
       
   279                             break;
       
   280                         }
       
   281                     }
       
   282                 } catch (SecurityException e) {
       
   283                     // catch and ignore because permission only required
       
   284                     // for one entry on path (at most)
       
   285                     securityExceptions = true;
       
   286                 }
       
   287             }
       
   288             if (securityExceptions) {
       
   289                 System.err.println("SecurityExceptions thrown on loading testkeys");
       
   290             }
       
   291             ssl = sslContext;
       
   292         }
       
   293 
       
   294         private SSLContext init(InputStream i) throws IOException {
       
   295             try {
       
   296                 char[] passphrase = "passphrase".toCharArray();
       
   297                 KeyStore ks = KeyStore.getInstance("JKS");
       
   298                 ks.load(i, passphrase);
       
   299 
       
   300                 KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
       
   301                 kmf.init(ks, passphrase);
       
   302 
       
   303                 TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
       
   304                 tmf.init(ks);
       
   305 
       
   306                 SSLContext ssl = SSLContext.getInstance("TLS");
       
   307                 ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
       
   308                 return ssl;
       
   309             } catch (KeyManagementException | KeyStoreException |
       
   310                     UnrecoverableKeyException | CertificateException |
       
   311                     NoSuchAlgorithmException e) {
       
   312                 throw new RuntimeException(e.getMessage());
       
   313             }
       
   314         }
       
   315 
       
   316         public SSLContext get() {
       
   317             return ssl;
       
   318         }
       
   319     }
       
   320 }