/*
 * Created on 19-Jan-2006
 * Created by Paul Gardner
 * Copyright (C) 2006 Aelitis, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 * AELITIS, SAS au capital de 46,603.30 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 *
 */

package com.aelitis.azureus.core.networkmanager.impl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.RC4Engine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.gudy.azureus2.core3.util.Debug;

public class 
TransportCipher 
{
	private static boolean	internal_rc4	= true;	// force internal as we want 160 bit and JCE no supports it
	
	private Cipher		cipher;
	private RC4Engine	rc4_engine;
	
	public
	TransportCipher(
		String					algorithm,
		int						mode,
		SecretKeySpec			key_spec,
		AlgorithmParameterSpec	params )
	
		throws Exception
	{
    	cipher = Cipher.getInstance( algorithm );
    
    	cipher.init( mode, key_spec, params );
	}
	
	TransportCipher(
		String					algorithm,
		int						mode,
		SecretKeySpec			key_spec )
		
		throws Exception
	{
	    if ( algorithm.equals( "RC4" )){
	    	
	    	if ( !internal_rc4 ){
	    		
	    		try{
	    	    	cipher = Cipher.getInstance( algorithm );
	    		    
	    	    	cipher.init( mode, key_spec );
	    	    	
	    		}catch( Throwable e ){
	    			
	    			internal_rc4	= true;
	    		}
	    	}
	    	
	    	if ( internal_rc4 ){
	    		    		
	    		rc4_engine	= new RC4Engine();
	    		
	    		CipherParameters	params = new KeyParameter(key_spec.getEncoded());
	    		
	    		rc4_engine.init( mode == Cipher.ENCRYPT_MODE, params ); 
	    	}
	    	
	    	//System.out.println( "RC4 key: " + ByteFormatter.encodeString( key_spec.getEncoded()));
	    	
    			// skip first 1024 bytes of stream to protected against a Fluhrer, Mantin and Shamir attack
    	
	    	byte[]	temp = new byte[1024];
    	
	    	temp = update( temp );
	    	
	    	//System.out.println( "RC4: first discard = " + ByteFormatter.encodeString( temp, 0, 4 ));
	    }else{
	    	
	    	cipher = Cipher.getInstance( algorithm );
	    
	    	cipher.init( mode, key_spec );
	    }
	}
	
	protected byte[]
	update(
		byte[]	data )
	{
		return( update( data, 0, data.length ));
	}
	
	protected byte[]
   	update(
   		byte[]	data,
   		int		offset,
   		int		length )
   	{
		byte[]	result;
	
		if ( length == 0 ){
			
				// watch out, cipher.update returns NULL with 0 length input
			
			result = new byte[0];
			
		}else if ( cipher != null ){
						
			result = cipher.update( data, offset, length );
			
		}else{
						
			result = new byte[length];
			
			rc4_engine.processBytes( data, offset, length, result, 0 );
		}
	
		return( result );
   	}
	           	
	protected void
	update(
		ByteBuffer	source_buffer,
		ByteBuffer	target_buffer )
	
		throws IOException
	{
		try{
			// TODO: 1.5 supports update( ByteBuffer, ByteBuffer )
			
			byte[]	source_bytes;
			int		offset;
			int		length	= source_buffer.remaining();
			
			if ( source_buffer.hasArray()){
				
				source_bytes 	= source_buffer.array();
				
				offset			= source_buffer.arrayOffset() + source_buffer.position();
				
			}else{
				
				source_bytes 	= new byte[length];
				
				offset			= 0;
				
				source_buffer.get( source_bytes );
			}
			
			byte[]	target_bytes = update( source_bytes, offset, length ); 
			
			source_buffer.position( source_buffer.limit());
			
			target_buffer.put( target_bytes );
			
		}catch( Throwable e ){
						
			throw( new IOException( Debug.getNestedExceptionMessage( e )));
		}
	}
	
	public String
	getName()
	{
		if ( cipher != null ){
			
			String	s = cipher.getAlgorithm();
			
			int	pos = s.indexOf("/");
			
			if ( pos != -1 ){
				
				s = s.substring(0,pos);
			}
			
			if ( s.equals( "RC4" )){
				
				s = "RC4-160";
				
			}else{
				
				s += "-" + cipher.getBlockSize()*8;
			}
			
			return( s );
		}else{
			
			return( "RC4-160" );
		}
	}
}
