/* $Id$
 *
 * Copyright 2007-2008 Cisco Systems Inc.
 *
 * 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 org.apache.etch.util.core.io;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketException;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.etch.util.FlexBuffer;
import org.apache.etch.util.Resources;
import org.apache.etch.util.URL;


/**
 * Implementation of connection which handles a secure tcp connection.
 */
public class TlsConnection extends TcpTransport
{
	/**
	 * Term on uri which specifies whether server certificate should be authenticated.
	 */
    public static final String AUTH_REQD = "TlsConnection.authReqd";
	
	/**
	 * Constructs the Connection from a uri.
	 * @param socket
	 * @param uri
	 * @param resources
	 */
	public TlsConnection( SSLSocket socket, String uri, Resources resources )
	{
		this( socket, new URL( uri ), resources );
	}
	
	/**
	 * Constructs the Connection from a uri.
	 * @param socket
	 * @param uri
	 * @param resources
	 */
	public TlsConnection( SSLSocket socket, URL uri, Resources resources )
	{
		super( uri, resources );
		setDefaultAuthReqd( uri.getBooleanTerm( AUTH_REQD, true ) );
		
		if (socket == null)
		{
			String h = uri.getHost();
			if (h == null)
				throw new IllegalArgumentException( "host == null" );
			
			Integer p = uri.getPort();
			if (p == null)
				throw new IllegalArgumentException( "port == null" );
			
			if (p <= 0 || p > 65535)
				throw new IllegalArgumentException( "port <= 0 || port > 65535" );
			
			this.socket = null;
			this.host = h;
			this.port = p;
		}
		else
		{
			this.socket = socket;
			this.host = null;
			this.port = 0;
		}
	}

	private final String host;
	
	private final int port;

	private void setDefaultAuthReqd( boolean authReqd )
	{
		this.authReqd = authReqd;
	}
    
    private boolean authReqd;
	
	@Override
	public String toString()
	{
		Socket s = socket;
		
		if (s != null)
			return String.format(
				"TlsConnection(up, %s:%d, %s:%d)",
				s.getLocalAddress(), s.getLocalPort(),
				s.getInetAddress(), s.getPort() );
		
		return String.format( "TlsConnection(down, %s:%d)", host, port );
	}

	@Override
	protected boolean isServer()
	{
		return host == null;
	}

	@Override
	protected Socket newSocket() throws Exception
	{
		SSLSocketFactory sslsocketfactory = null;
		if (authReqd)
		{
			sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
		}
		else
		{
			TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
			{
				public java.security.cert.X509Certificate[] getAcceptedIssuers()
				{
					return null;
				}

				public void checkClientTrusted(
					java.security.cert.X509Certificate[] certs, String authType )
				{
					// ignore
				}

				public void checkServerTrusted(
					java.security.cert.X509Certificate[] certs, String authType )
				{
					// ignore
				}
			} };
			SSLContext sslc = SSLContext.getInstance( "SSLv3" );
			sslc.init( null, trustAllCerts, null );
			sslsocketfactory = sslc.getSocketFactory();
		}
		return sslsocketfactory.createSocket( host, port );
	}

	@Override
	protected void readSocket() throws Exception
	{
		final InputStream is = inputStream;
		if (is == null)
			throw new IOException( "socket closed" );
		
		// TODO allow setting input buffer size.
		final FlexBuffer buf = new FlexBuffer( new byte[8192] );
		
		try
		{
			while (isStarted())
			{
//				System.out.println( "reading" );
				int n = is.read( buf.getBuf() );
//				System.out.println( "read "+n );
				
				if (n <= 0)
					break;
				
				buf.setLength( n );
				buf.setIndex( 0 );
				fireData( buf );
			}
		}
		catch ( SSLHandshakeException ex )
		{
			SSLHandshakeException exc = new SSLHandshakeException(
				" Problem in setting up SSL Connection. Either disable"
					+ " server authentication by using flag TlsConnection.authReqd=false in uri or provide"
					+ " a truststore location which contains the valid certificate" );
			// exc.setStackTrace( ex.getStackTrace() );
			throw exc;
		}
		catch ( SocketException e )
		{
//			Log.report( "readSocketFailed",
//				"s.local", s.getLocalSocketAddress(),
//				"s.remote", s.getRemoteSocketAddress(),
//				"e", e.toString() );
			
			String msg = e.getMessage();
			if (msg == null)
				throw e;
			
			msg = msg.toLowerCase();
			
			if (msg.indexOf( "socket closed" ) >= 0)
				return;
			
			if (msg.indexOf( "recv failed" ) >= 0)
				return;
			
			if (msg.indexOf( "connection reset" ) >= 0)
				return;
			
			throw e;
		}
	}
	
	@Override
	public void shutdownInput() throws IOException
	{
//		checkSocket().shutdownInput();
		checkSocket().close();
	}
	
	@Override
	public void shutdownOutput() throws IOException
	{
//		checkSocket().shutdownOutput();
		checkSocket().close();
	}
}

@@ -5092,32 +5092,95 @@
 %09%09%09%09return;%0A%09%09%09%0A
+%09%09%09if (msg.indexOf( %22Socket is closed%22 ) %3E= 0)%0A%09%09%09%09return;%0A%09%09%09%0A
 %09%09%09if (msg.index

fix for ETCH-57: Sometimes closing a TlsConnection normally will result in an IOException with Socket is closed.