/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package libcore.javax.net.ssl;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLSocket;

/**
 * TestSSLSocketPair is a convenience class for other tests that want
 * a pair of connected and handshaked client and server SSLSockets for
 * testing.
 */
public final class TestSSLSocketPair {
    public final TestSSLContext c;
    public final SSLSocket server;
    public final SSLSocket client;

    private TestSSLSocketPair (TestSSLContext c,
                               SSLSocket server,
                               SSLSocket client) {
        this.c = c;
        this.server = server;
        this.client = client;
    }

    public void close() {
        c.close();
        try {
            server.close();
            client.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * based on test_SSLSocket_startHandshake
     */
    public static TestSSLSocketPair create () {
        TestSSLContext c = TestSSLContext.create();
        SSLSocket[] sockets = connect(c, null, null);
        return new TestSSLSocketPair(c, sockets[0], sockets[1]);
    }

    /**
     * Create a new connected server/client socket pair within a
     * existing SSLContext. Optionally specify clientCipherSuites to
     * allow forcing new SSLSession to test SSLSessionContext
     * caching. Optionally specify serverCipherSuites for testing
     * cipher suite negotiation.
     */
    public static SSLSocket[] connect (final TestSSLContext context,
                                       final String[] clientCipherSuites,
                                       final String[] serverCipherSuites) {
        try {
            final SSLSocket client = (SSLSocket)
                context.clientContext.getSocketFactory().createSocket(context.host, context.port);
            final SSLSocket server = (SSLSocket) context.serverSocket.accept();

            ExecutorService executor = Executors.newFixedThreadPool(2);
            Future s = executor.submit(new Callable<Void>() {
                    public Void call() throws Exception {
                        if (serverCipherSuites != null) {
                            server.setEnabledCipherSuites(serverCipherSuites);
                        }
                        server.startHandshake();
                        return null;
                    }
                });
            Future c = executor.submit(new Callable<Void>() {
                    public Void call() throws Exception {
                        if (clientCipherSuites != null) {
                            client.setEnabledCipherSuites(clientCipherSuites);
                        }
                        client.startHandshake();
                        return null;
                    }
                });
            executor.shutdown();

            // catch client and server exceptions separately so we can
            // potentially log both.
            Exception serverException;
            try {
                s.get(30, TimeUnit.SECONDS);
                serverException = null;
            } catch (Exception e) {
                serverException = e;
                e.printStackTrace();
            }
            Exception clientException;
            try {
                c.get(30, TimeUnit.SECONDS);
                clientException = null;
            } catch (Exception e) {
                clientException = e;
                e.printStackTrace();
            }
            if (serverException != null) {
                throw serverException;
            }
            if (clientException != null) {
                throw clientException;
            }
            return new SSLSocket[] { server, client };
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

