//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.ssl;

import java.util.Iterator;
import java.util.Vector;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLSession;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HandshakeCompletedEvent;

/**
 * An implementation of java.net.ssl.SSLSocket which does the actual work
 * when operations of javax.net.ssl.SSLSocket are called.
 *
 * @author Ming Yung
 */
final class SSLSocket extends javax.net.ssl.SSLSocket
{
  private SSLContext ctx = null;
  private SSLSession session = null;
  private HandShaker handShaker = null;
  private String[] enabledCipherSuites = null;
  private boolean clientAuthRequired = false;
  private boolean sessionCreationEnabled = true;
  private SessionCache sessionCache = null;
  private Object readLock = new Object();
  private Object writeLock = new Object();
  private SSLDataInputStream in = null;
  private SSLDataOutputStream out = null;

  ////////////////////////
  // Constructors
  ////////////////////////
  
  protected SSLSocket()
  {
    super();
  }

  protected SSLSocket(String host, int port, PrivateKey privKey,
                      Vector clientCerts, String[] suites,
                      Vector trustedCerts, SessionCache cache,
                      SecureRandom rand) 
    throws UnknownHostException, IOException
  {
    super(host, port);
   
    init(rand, privKey, clientCerts, trustedCerts, cache);
    setEnabledCipherSuites(suites);
  }  

  protected SSLSocket(String host, int port, InetAddress clientHost,
                      int clientPort, PrivateKey privKey, Vector clientCerts,
                      String[] suites, Vector trustedCerts, 
                      SessionCache cache, SecureRandom rand) 
    throws UnknownHostException, IOException
  {
    super(host, port, clientHost, clientPort);

    init(rand, privKey, clientCerts, trustedCerts, cache);
    setEnabledCipherSuites(suites);
  }  

  protected SSLSocket(InetAddress address, int port, PrivateKey privKey,
                      Vector clientCerts, String[] suites, Vector trustedCerts,
                      SessionCache cache, SecureRandom rand) 
    throws UnknownHostException, IOException
  {
    super(address, port);

    init(rand, privKey, clientCerts, trustedCerts, cache);
    setEnabledCipherSuites(suites);
  }  

  protected SSLSocket(InetAddress host, int port, InetAddress clientHost,
                      int clientPort, PrivateKey privKey, Vector clientCerts,
                      String[] suites, Vector trustedCerts, 
                      SessionCache cache, SecureRandom rand) 
    throws UnknownHostException, IOException
  {
    super(host, port, clientHost, clientPort);

    init(rand, privKey, clientCerts, trustedCerts, cache);
    setEnabledCipherSuites(suites);
  }  

  /**
   * Called by SSLServerSocket
   */
  void initialize(SecureRandom rand, PrivateKey privKey, 
                            Vector serverCerts, Vector trustedCerts, 
                            String[] suites,
                            boolean clientAuthRequired,
                            SessionCache cache)
    throws UnknownHostException, IOException
  {
    ctx = new SSLContext(super.getInputStream(), super.getOutputStream());

    this.sessionCache = cache;

    handShaker 
      = new ServerHandShaker(this, rand, privKey, serverCerts, trustedCerts);
    setEnabledCipherSuites(suites);
    setNeedClientAuth(clientAuthRequired);
    ctx.setHandShaker(handShaker);

    in = new SSLDataInputStream(ctx.getInputStream(), this.readLock, 
                                handShaker);
    out = new SSLDataOutputStream(ctx.getOutputStream(), this.writeLock,
                                  this.readLock, ctx);
  }  

  private void init(SecureRandom rand, PrivateKey privKey, Vector clientCerts, 
                    Vector trustedCerts, SessionCache cache) 
    throws UnknownHostException, IOException
  {
    ctx = new SSLContext(super.getInputStream(), super.getOutputStream());

    this.sessionCache = cache;

    handShaker 
      = new ClientHandShaker(this, rand, privKey, clientCerts, trustedCerts);
    ctx.setHandShaker(handShaker);

    in = new SSLDataInputStream(ctx.getInputStream(), this.readLock, 
                                handShaker);
    out = new SSLDataOutputStream(ctx.getOutputStream(), this.writeLock,
                                  this.readLock, ctx);
  }  

  public void setEnabledCipherSuites(String[] suites)
  {
    this.enabledCipherSuites = suites;
    handShaker.setEnabledCipherSuites(suites);
  }

  public String[] getEnabledCipherSuites()
  {
    return enabledCipherSuites;
  }

  public String[] getSupportedCipherSuites()
  {
    return CipherSuites.suiteName;
  }

  public void setEnableSessionCreation(boolean flag)
  {
    this.sessionCreationEnabled = flag;
  }

  public boolean getEnableSessionCreation()
  {
    return this.sessionCreationEnabled;
  }

  public void setNeedClientAuth(boolean flag)
  {
    this.clientAuthRequired = flag;
    ((ServerHandShaker)handShaker).setClientAuthRequired(flag);
  }

  public boolean getNeedClientAuth()
  {
    return this.clientAuthRequired;
  }

  public void setUseClientMode(boolean falg)
  {
  }

  public boolean getUseClientMode()
  {
    return false;
  }

  SSLContext getContext()
  {
    return ctx;
  }

  void setSession(SSLSession session)
  {
    this.session = session;
  }

  public SSLSession getSession()
  {
    return this.session;
  }

  public void addHandshakeCompletedListener(
    HandshakeCompletedListener listener)
  {
    this.handShaker.addHandshakeCompletedListener(listener);
  }

  public void removeHandshakeCompletedListener(
    HandshakeCompletedListener listener)
  {
  }

  public void startHandshake() throws IOException
  {
    handShaker.startHandShake();
  }

  public InputStream getInputStream() throws IOException
  {
    return in;
  }

  public OutputStream getOutputStream() throws IOException
  {
    return out;
  }

  public void close() throws IOException
  {
    try
    {
      synchronized (readLock)
      {
        while (!handShaker.handShakeCompleted())
        {
          readLock.notifyAll();
          readLock.wait();
        }

        out.flush();
        handShaker.cleanup();
      }
    }
    catch (InterruptedException e)
    {
      e.printStackTrace();
    }
    catch (IOException e)
    {
      //e.printStackTrace();
      //ignore
    }

    super.close();
  }

  SessionCache getSessionCache()
  {
    return this.sessionCache;
  }

  Object getReadLock()
  {
    return this.readLock;
  }

  Object getWriteLock()
  {
    return this.writeLock;
  }
}
