//////////////////////////////////////////////////////////////////////////// 
// 
// 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.Arrays;
import java.io.IOException;
import java.io.InputStream;
import java.io.FilterInputStream; 

import javax.net.ssl.SSLHandshakeException;

/**
 * An input stream for application data
 *
 * @author Ming Yung
 */
class SSLDataInputStream extends FilterInputStream
{
  private ProtocolUnitInputStream is;
  private static final int DEFAULT_BUFFER_LEN = 1024;
  private byte[] buffer = new byte[DEFAULT_BUFFER_LEN];
  private int bufferOff = 0;
  private int bufferLen = 0;
  private boolean eof = false;
  private byte[] data;
  private Object readLock;
  private HandShaker handShaker;
  private byte[] readBuffer = new byte[1];

  SSLDataInputStream(InputStream is, Object readLock, HandShaker handShaker)
  {
    super(is);
    this.is = (ProtocolUnitInputStream)is;
    this.readLock = readLock;
    this.handShaker = handShaker;
  }

  public int available()
  {
    return bufferLen;
  }

  public void close() throws IOException
  {
    super.close();
    //System.out.println("readclose()");
  }

  public int read() throws IOException
  {
    read(readBuffer);
    return (readBuffer[0] & 0xff);
  }

  public int read(byte[] b) throws IOException
  {
    return read(b, 0, b.length);
  }

  public int read(byte[] b, int offset, int len) throws IOException
  {
    if (this.handShaker.handShakeNotYetBegun())
    {
      if (this.handShaker instanceof ClientHandShaker)
        this.handShaker.startHandShake();
    }

    int retval = 0;

    if (eof) return -1;

    if (len <= 0) return 0;

    if (len <= bufferLen)
    {
      System.arraycopy(buffer, bufferOff, b, offset, len); 
      bufferOff += len;
      bufferLen -= len;
      return len;
    }

    int extra = len - bufferLen;

    System.arraycopy(buffer, bufferOff, b, offset, bufferLen);

    retval += bufferLen;
    offset += bufferLen;
    bufferOff = 0;
    bufferLen = 0;

    synchronized (readLock)
    {
      try
      {
        while (!this.handShaker.handShakeCompleted())
        {
          readLock.wait();
        }

        if (this.handShaker.badCompletion())
          throw new SSLHandshakeException("Handshake failure"); 

        while (true)
        {
          SSLProtocolUnit pu = is.readProtocolUnit();

          if (pu instanceof ApplicationData)
          { 
            data = ((ApplicationData)pu).getBytes();
            if (Debug.debug >= Debug.DEBUG_RECORD)
              Debug.debug("got some data");
          }
          else if (pu instanceof HandShake)
          {
            if (Debug.debug >= Debug.DEBUG_RECORD)
              Debug.debug("handshake in data");

            this.handShaker.nextMessage(pu);
            readLock.notifyAll();
            break;
          }
          else if (pu instanceof Alert)
          {
            break;
          }
          else if (pu == null)
          {
            eof = true;
            break;
          }
  
          if (data.length + bufferLen > buffer.length)
          {
            byte[] temp = (byte[])buffer.clone();
            buffer = new byte[data.length + bufferLen];
            System.arraycopy(temp, 0, buffer, 0, bufferLen);
          }

          System.arraycopy(data, 0, buffer, bufferLen, data.length);
          bufferLen += data.length;

          //if (extra <= bufferLen) // how do we prevent indefinite blocking?? 
          break;
        }
      }
      catch (InterruptedException e)
      {
        //wake up
      }

      int copyLen;
      if (extra <= bufferLen)
      {
        copyLen = extra;
        bufferLen -= copyLen;
      }
      else
      {
        copyLen = bufferLen;
        bufferLen = 0;
      }
      
      System.arraycopy(buffer, bufferOff, b, offset, copyLen);
      bufferOff += copyLen;
      retval += copyLen;
      return retval; 
    }
  }
}
