FileDocCategorySizeDatePackage
ProtocolDecoderPHE.javaAPI DocAzureus 3.0.3.452194Fri Mar 02 09:02:00 GMT 2007com.aelitis.azureus.core.networkmanager.impl

ProtocolDecoderPHE

public class ProtocolDecoderPHE extends ProtocolDecoder

Fields Summary
private static final org.gudy.azureus2.core3.logging.LogIDs
LOGID
private static final byte
CRYPTO_PLAIN
private static final byte
CRYPTO_RC4
private static final byte
CRYPTO_XOR
private static final byte
CRYPTO_AES
private static final String
DH_P
private static final String
DH_G
private static final int
DH_L
private static final int
DH_SIZE_BYTES
public static final int
MIN_INCOMING_INITIAL_PACKET_SIZE
private static final BigInteger
DH_P_BI
private static final BigInteger
DH_G_BI
private static KeyPairGenerator
dh_key_generator
private static long
last_dh_incoming_key_generate
private static final int
BLOOM_RECREATE
private static final int
BLOOM_INCREASE
private static com.aelitis.azureus.core.util.bloom.BloomFilter
generate_bloom
private static long
generate_bloom_create_time
private static boolean
crypto_ok
private static final String
RC4_STREAM_ALG
private static final String
RC4_STREAM_CIPHER
private static final int
RC4_STREAM_KEY_SIZE
private static final int
RC4_STREAM_KEY_SIZE_BYTES
private static final int
PADDING_MAX
private static final int
PADDING_MAX_NORMAL
private static final int
PADDING_MAX_LIMITED
private static final Random
random
private static Map
global_shared_secrets
private static final byte
SUPPORTED_PROTOCOLS
private static byte
MIN_CRYPTO
private static final int
PS_OUTBOUND_1
private static final int
PS_OUTBOUND_2
private static final int
PS_OUTBOUND_3
private static final int
PS_OUTBOUND_4
private static final int
PS_INBOUND_1
private static final int
PS_INBOUND_2
private static final int
PS_INBOUND_3
private static final int
PS_INBOUND_4
public static final byte[]
KEYA_IV
public static final byte[]
KEYB_IV
public static final byte[]
REQ1_IV
public static final byte[]
REQ2_IV
public static final byte[]
REQ3_IV
public static final byte[]
VC
private TransportHelper
transport
private ByteBuffer
write_buffer
private ByteBuffer
read_buffer
private ProtocolDecoderAdapter
adapter
private KeyAgreement
key_agreement
private byte[]
dh_public_key_bytes
private byte[]
shared_secret
private byte[]
secret_bytes
private ByteBuffer
initial_data_out
private ByteBuffer
initial_data_in
private TransportCipher
write_cipher
private TransportCipher
read_cipher
private byte[]
padding_skip_marker
private byte
my_supported_protocols
private byte
selected_protocol
private boolean
outbound
private int
protocol_state
private int
protocol_substate
private boolean
handshake_complete
private int
bytes_read
private int
bytes_written
private long
last_read_time
private TransportHelperFilter
filter
private boolean
delay_outbound_4
private boolean
processing_complete
private org.gudy.azureus2.core3.util.AEMonitor
process_mon
Constructors Summary
public ProtocolDecoderPHE(TransportHelper _transport, byte[] _shared_secrets, ByteBuffer _header, ByteBuffer _initial_data, ProtocolDecoderAdapter _adapter)

	
	 
	
						
							
							
							
				 
	
		 
	
		super( false );
		
		if ( !isCryptoOK()){
			
			throw( new IOException( "PHE crypto broken" ));
		}
		
		transport			= _transport;
		initial_data_out	= _initial_data;
		adapter				= _adapter;
		
		if ( _shared_secrets == null || _shared_secrets.length == 0 ){
			
			shared_secret	= new byte[0];
			
		}else{
			
			if ( _shared_secrets.length == 1 ){
			
				shared_secret	= _shared_secrets[0];

			}else{
			
				shared_secret	= _shared_secrets[random.nextInt( _shared_secrets.length )];
			}

			// System.out.println( "outbound - using crypto secret " + ByteFormatter.encodeString( shared_secret ));     
		}
		
		outbound	= _header == null;
				
		my_supported_protocols = SUPPORTED_PROTOCOLS;
				
		if ( outbound ){
			
			//if ( !NetworkManager.REQUIRE_CRYPTO_HANDSHAKE ){				
			//	throw( new IOException( "Crypto encoder selected for outbound but crypto not required" ));
			//}
			
				// outbound connection, we require a certain minimal level of support
			
			my_supported_protocols = MIN_CRYPTO;
			
		}else{
			
				// incoming. If we require crypto then we use minimum otherwise available
			
			if ( NetworkManager.REQUIRE_CRYPTO_HANDSHAKE ){
				
				my_supported_protocols = MIN_CRYPTO;
			}
		}
		
		initCrypto();

		try{
			process_mon.enter();
		
			transport.registerForReadSelects(
				new TransportHelper.selectListener()
				{
				   	public boolean 
			    	selectSuccess(
			    		TransportHelper	helper, 
			    		Object 			attachment )
				   	{
				   		return( ProtocolDecoderPHE.this.selectSuccess( helper, attachment, false ));
				   	}
	
			        public void 
			        selectFailure(
			        	TransportHelper	helper,
			        	Object 			attachment, 
			        	Throwable 		msg)
			        {
			        	ProtocolDecoderPHE.this.selectFailure( helper, attachment, msg );
			        }
				},
				null );
			
			transport.registerForWriteSelects(
					new TransportHelper.selectListener()
					{
					   	public boolean 
				    	selectSuccess(
				    		TransportHelper	helper, 
				    		Object 			attachment )
					   	{
					   		return( ProtocolDecoderPHE.this.selectSuccess( helper, attachment, true ));
					   	}
	
				        public void 
				        selectFailure(
				        	TransportHelper	helper,
				        	Object 			attachment, 
				        	Throwable 		msg)
				        {
				        	ProtocolDecoderPHE.this.selectFailure( helper, attachment, msg );
				        }
					},
					null );
			
			transport.pauseWriteSelects();
			
			if ( outbound ){
			
				protocol_state	= PS_OUTBOUND_1;
	
				transport.pauseReadSelects();
				
			}else{
				
				protocol_state	= PS_INBOUND_1;
	
				read_buffer = ByteBuffer.allocate( dh_public_key_bytes.length );					
					
				read_buffer.put( _header );
			
				bytes_read += _header.limit();
			}
		}finally{
			
			process_mon.exit();
		}
		
		process();
	
Methods Summary
public static voidaddSecretsSupport(byte[][] secrets)

		for (int i=0;i<secrets.length;i++){
			
			SHA1Hasher hasher = new SHA1Hasher();
	   		
	   		hasher.update( REQ2_IV );
	   		hasher.update( secrets[i] );
	   		
	   		byte[]	encoded = hasher.getDigest();
			                  	
			synchronized( global_shared_secrets ){
				
				global_shared_secrets.put( new HashWrapper( encoded ), secrets[i] );
			}
		}
	
protected byte[]bigIntegerToBytes(java.math.BigInteger bi, int num_bytes)

		String	str = bi.toString(16);
		
		while( str.length() < num_bytes*2 ){
			str = "0" + str;
		}
		
		return( ByteFormatter.decodeString(str));
	
protected java.math.BigIntegerbytesToBigInteger(byte[] bytes, int offset, int len)

		
		return( new BigInteger( ByteFormatter.encodeString( bytes, offset, len  ), 16 ));
	
protected voidcomplete()

		// System.out.println( (outbound?"out: ":"in :") + " complete, r " + bytes_read + ", w " + bytes_written + ", initial data = " + initial_data_in.length + "/" + initial_data_out.length );

		processing_complete	= true;
		
		adapter.decodeComplete( this, initial_data_out );	
	
protected voidcompleteDH(byte[] buffer)

		try{			
	        BigInteger	other_dh_y = bytesToBigInteger( buffer, 0, DH_SIZE_BYTES );
	        
	        KeyFactory dh_key_factory = KeyFactory.getInstance("DH");
	        	    
		    PublicKey other_public_key = dh_key_factory.generatePublic( new DHPublicKeySpec( other_dh_y, DH_P_BI, DH_G_BI ));
	        		
		    key_agreement.doPhase( other_public_key, true );
		    
		    secret_bytes = key_agreement.generateSecret();
			    
		    adapter.gotSecret( secret_bytes );
		    
		    // System.out.println( "secret = " + ByteFormatter.encodeString( secret_bytes ));
		    
		}catch( Throwable e ){
			
			throw( new IOException( Debug.getNestedExceptionMessage(e)));
		}
	
protected voidfailed(java.lang.Throwable cause)

		// System.out.println( (outbound?"out: ":"in :") + " failed, " + cause.getMessage());

		processing_complete	= true;
		
		transport.cancelReadSelects();
		
		transport.cancelWriteSelects();

		adapter.decodeFailed( this, cause );
	
protected static java.security.KeyPairgenerateDHKeyPair(TransportHelper transport, boolean outbound)

		synchronized( dh_key_generator ){
			
			if ( !outbound ){
				
				byte[]	address = transport.getAddress().getAddress().getAddress();
				
				int	hit_count = generate_bloom.add( address );
				
				long	now = SystemTime.getCurrentTime();
	
					// allow up to 10% bloom filter utilisation
				
				if ( generate_bloom.getSize() / generate_bloom.getEntryCount() < 10 ){
					
					generate_bloom = BloomFilterFactory.createAddRemove4Bit(generate_bloom.getSize() + BLOOM_INCREASE );
					
					generate_bloom_create_time	= now;
					
		     		Logger.log(	new LogEvent(LOGID, "PHE bloom: size increased to " + generate_bloom.getSize()));
	
				}else if ( now < generate_bloom_create_time || now - generate_bloom_create_time > BLOOM_RECREATE ){
					
					generate_bloom = BloomFilterFactory.createAddRemove4Bit(generate_bloom.getSize());
					
					generate_bloom_create_time	= now;
				}
					
				if ( hit_count >= 15 ){
					
		     		Logger.log(	new LogEvent(LOGID, "PHE bloom: too many recent connection attempts from " + transport.getAddress()));
		     		
					throw( new IOException( "Too many recent connection attempts (phe)"));
				}
				
				long	since_last = now - last_dh_incoming_key_generate;
				
				long	delay = 100 - since_last;
				
					// limit key gen operations to 10 a second
				
				if ( delay > 0 && delay < 100 ){
					
					try{
						Thread.sleep( delay );
						
					}catch( Throwable e ){
					}
				}
				
				last_dh_incoming_key_generate = now;
			}
			
			KeyPair	res = dh_key_generator.generateKeyPair();
			
			return( res );
		}
	
public TransportHelperFiltergetFilter()

		return( filter );
	
public longgetLastReadTime()

		long	now = SystemTime.getCurrentTime();
		
		if ( last_read_time > now ){
			
			last_read_time	= now;
		}
		
		return( last_read_time );
	
public static intgetMaxIncomingInitialPacketSize(boolean min_overheads)

       
       
    
    		 
    
    	return( MIN_INCOMING_INITIAL_PACKET_SIZE + (min_overheads?PADDING_MAX_LIMITED:PADDING_MAX_NORMAL)/2 );
    
protected intgetPaddingMax()

		if ( transport.minimiseOverheads()){
			
			return( PADDING_MAX_LIMITED );
			
		}else{
			
			return( PADDING_MAX_NORMAL );
		}
	
protected static synchronized byte[]getRandomPadding(int max_len)

		byte[]	bytes = new byte[ random.nextInt(max_len)];
		
		random.nextBytes(bytes);
		
		return( bytes );
	
public java.lang.StringgetString()

		return( "state=" + protocol_state + ",sub=" + protocol_substate + ",in=" + bytes_read + ",out=" + bytes_written);
	
protected static synchronized byte[]getZeroPadding(int max_len)

   		byte[]	bytes = new byte[ random.nextInt(max_len)];
   		
   		return( bytes );
   	
protected voidhandshakeComplete()

		
		if ( selected_protocol == CRYPTO_PLAIN ){
			
			filter = new TransportHelperFilterTransparent( transport, true );
									
		}else if ( selected_protocol == CRYPTO_XOR ){
		
			filter = new TransportHelperFilterStreamXOR( transport, secret_bytes );
						
		}else if ( selected_protocol == CRYPTO_RC4 ){
		
			filter = new TransportHelperFilterStreamCipher( 
						transport,
						read_cipher,
						write_cipher );

			/*
		}else if ( selected_protocol == CRYPTO_AES ){
			
			try{
		        SecretKeySpec	secret_key_spec = new SecretKeySpec( secret_bytes, 32, AES_STREAM_KEY_SIZE_BYTES, AES_STREAM_ALG );
			        		        
		        AlgorithmParameterSpec	spec = 	new IvParameterSpec( secret_bytes, 48, AES_STREAM_KEY_SIZE_BYTES );
		        
		        write_cipher 	= new TCPTransportCipher( AES_STREAM_CIPHER, Cipher.ENCRYPT_MODE, secret_key_spec, spec );
				    
		        read_cipher 	= new TCPTransportCipher( AES_STREAM_CIPHER, Cipher.DECRYPT_MODE, secret_key_spec, spec );
		        
				filter = new TCPTransportHelperFilterStreamCipher( 
						helper,
						read_cipher,
						write_cipher );
				
			}catch( Throwable e ){
				
				throw( new IOException( "AES crypto init failed: " + Debug.getNestedExceptionMessage(e)));
			}
		*/
		
		
		}else{
			
			throw( new IOException( "Invalid selected protocol '" + selected_protocol + "'" ));
		}	
			
		if ( initial_data_in != null ){
			
			filter = new TransportHelperFilterInserter(	filter, initial_data_in );
		}
		
		handshake_complete	= true;
	
protected voidinitCrypto()

		try{
	        KeyPair key_pair = generateDHKeyPair( transport, outbound );
	    	    
	        key_agreement = KeyAgreement.getInstance("DH");
	        
	        key_agreement.init(key_pair.getPrivate());
	       
	        DHPublicKey	dh_public_key = (DHPublicKey)key_pair.getPublic();
	        
	        BigInteger	dh_y = dh_public_key.getY();
	        
	        dh_public_key_bytes = bigIntegerToBytes( dh_y, DH_SIZE_BYTES );
	        
		}catch( Throwable e ){
			
			throw( new IOException( Debug.getNestedExceptionMessage(e)));
		}
	
public booleanisComplete(long now)

		return( processing_complete );
	
public static booleanisCryptoOK()

	
	
		try{
			DHParameterSpec dh_param_spec = new DHParameterSpec( DH_P_BI, DH_G_BI, DH_L );
			
			dh_key_generator = KeyPairGenerator.getInstance("DH");
	        
			dh_key_generator.initialize(dh_param_spec);
	        
			dh_key_generator.generateKeyPair();
	               	
		    byte[]	rc4_test_secret = new byte[RC4_STREAM_KEY_SIZE_BYTES];

		    SecretKeySpec	rc4_test_secret_key_spec = new SecretKeySpec(rc4_test_secret, 0, RC4_STREAM_KEY_SIZE_BYTES, RC4_STREAM_ALG );
		        		        
		    TransportCipher rc4_cipher = new TransportCipher( RC4_STREAM_CIPHER, Cipher.ENCRYPT_MODE, rc4_test_secret_key_spec );
		         
		    rc4_cipher = new TransportCipher( RC4_STREAM_CIPHER, Cipher.DECRYPT_MODE, rc4_test_secret_key_spec );
	        
		    /*
			try{
				byte[]	aes_test_secret = new byte[AES_STREAM_KEY_SIZE_BYTES];
	        	 
				SecretKeySpec	aes_test_secret_key_spec = new SecretKeySpec(aes_test_secret, 0, AES_STREAM_KEY_SIZE_BYTES, AES_STREAM_ALG );
		        	        
				AlgorithmParameterSpec	spec = 	new IvParameterSpec( aes_test_secret );
		        
		        TCPTransportCipher aes_cipher = new TCPTransportCipher( AES_STREAM_CIPHER, Cipher.ENCRYPT_MODE, aes_test_secret_key_spec, spec );
		        
		        aes_cipher = new TCPTransportCipher( AES_STREAM_CIPHER, Cipher.DECRYPT_MODE, aes_test_secret_key_spec, spec );
		        
		        aes_ok	= true;
		        
			}catch( Throwable e ){
				
				Logger.log(	new LogEvent(LOGID, "AES Unavailable", e ));
			}
	        */
		    
	        crypto_ok	= true;
	        
	     	if (Logger.isEnabled()){
	     		
        		Logger.log(	new LogEvent(LOGID, "PHE crypto initialised" ));
	     	}
		}catch( NoClassDefFoundError e ){
			
				// running without PHE classes, not such a severe error
      	
			Logger.log(	new LogEvent(LOGID, "PHE crypto disabled as classes unavailable" ));
			
			crypto_ok	= false;
			
		}catch( Throwable e ){
				     		
        	Logger.log(	new LogEvent(LOGID, "PHE crypto initialisation failed", e ));
			
			crypto_ok	= false;
		}
	
		return( crypto_ok );
	
protected voidprocess()

		try{
			process_mon.enter();
			
			if ( handshake_complete ){
				
				Debug.out( "Handshake process already completed" );
				
				return;
			}
			
			boolean	loop = true;
		
			while( loop ){
					
				// System.out.println( this + ":" + (outbound?"out: ":"in : ") + protocol_state + "/" + protocol_substate + ": r " + bytes_read + " - " + read_buffer + ", w " + bytes_written + " - " + write_buffer );
				
				if ( protocol_state == PS_OUTBOUND_1 ){
					
					if ( write_buffer == null ){
						
							// A sends B Ya + Pa
						
						byte[]	padding_a = getRandomPadding(getPaddingMax()/2);	// note that /2 also used in calculating max initial packet size above									
						
						write_buffer = ByteBuffer.allocate( dh_public_key_bytes.length + padding_a.length );
												
						write_buffer.put( dh_public_key_bytes );
						
						write_buffer.put( padding_a );
						
						write_buffer.flip();
					}
					
					write( write_buffer );
						
					if ( !write_buffer.hasRemaining()){
					
						write_buffer	= null;
					
						protocol_state	= PS_INBOUND_2;
					}
	
				}else if ( protocol_state == PS_INBOUND_1 ){
					
						// B receives Ya 
	
					read( read_buffer );
						
					if ( !read_buffer.hasRemaining()){
											
						read_buffer.flip();
						
						byte[] other_dh_public_key_bytes = new byte[read_buffer.remaining()];
						
						read_buffer.get( other_dh_public_key_bytes );
							
						completeDH( other_dh_public_key_bytes );
						
				        read_buffer	= null;
				        		        
						protocol_state	= PS_OUTBOUND_2;
					}
					
				}else if ( protocol_state == PS_OUTBOUND_2 ){
					
						// B->A: Yb PadB

					if ( write_buffer == null ){
						
						byte[]	padding_b = getRandomPadding( getPaddingMax()/2 );
						
						write_buffer = ByteBuffer.allocate( dh_public_key_bytes.length + padding_b.length );
						
						write_buffer.put( dh_public_key_bytes );

						write_buffer.put( padding_b );
						
						write_buffer.flip();
					}
					
					write( write_buffer );
					
					if ( !write_buffer.hasRemaining()){
					
						write_buffer	= null;
					
						protocol_state	= PS_INBOUND_3;
					}
					
				}else if ( protocol_state == PS_INBOUND_2 ){
					
						// A receives: Yb
						
					if ( read_buffer == null ){
												
						read_buffer = ByteBuffer.allocate( dh_public_key_bytes.length );
					}					
						
					read( read_buffer );

					if ( !read_buffer.hasRemaining()){
						
						read_buffer.flip();
						
						byte[] other_dh_public_key_bytes = new byte[read_buffer.remaining()];
						
						read_buffer.get( other_dh_public_key_bytes );
							
						completeDH( other_dh_public_key_bytes );
						
							// A initiates SKEY so we can now set up crypto
						
						setupCrypto();
						
				        read_buffer	= null;
				        		        
						protocol_state	= PS_OUTBOUND_3;
					}
					
				}else if ( protocol_state == PS_OUTBOUND_3 ){
					
						// A->B: HASH('req1', S), HASH('req2', SKEY)^HASH('req3', S), ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA)
		
					if ( write_buffer == null ){
													
						int initial_data_out_len	= initial_data_out==null?0:initial_data_out.remaining();

							// padding_a here is half of the padding from before
						
						int	pad_max = getPaddingMax();
						
						byte[]	padding_a = getRandomPadding(pad_max/2);									

						byte[]	padding_c = getZeroPadding(pad_max);
						
						write_buffer = ByteBuffer.allocate( padding_a.length + 20 + 20 + ( VC.length + 4 + 2 + padding_c.length + 2 ) + initial_data_out_len );
						
						write_buffer.put( padding_a );
						
							// HASH('req1', S)
						
						SHA1Hasher	hasher = new SHA1Hasher();
						
						hasher.update( REQ1_IV );
						hasher.update( secret_bytes );
						
						byte[] sha1 = hasher.getDigest();
						
						write_buffer.put( sha1 );
								
							// HASH('req2', SKEY)^HASH('req3', S)
						
						hasher = new SHA1Hasher();
						
						hasher.update( REQ2_IV );
						hasher.update( shared_secret );
						
						byte[] sha1_1 = hasher.getDigest();
						
						hasher = new SHA1Hasher();
						
						hasher.update( REQ3_IV );
						hasher.update( secret_bytes );
						
						byte[] sha1_2 = hasher.getDigest();
						
						for (int i=0;i<sha1_1.length;i++){
							
							sha1_1[i] ^= sha1_2[i];
						}
						
						write_buffer.put( sha1_1 );
						
							// ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)
						
						write_buffer.put( write_cipher.update( VC ));
						
						write_buffer.put( write_cipher.update( new byte[]{ 0, 0, 0, my_supported_protocols }));
						
						write_buffer.put( write_cipher.update( new byte[]{ (byte)(padding_c.length>>8),(byte)padding_c.length }));
					
						write_buffer.put( write_cipher.update( padding_c ));
						
						write_buffer.put( write_cipher.update( new byte[]{ (byte)(initial_data_out_len>>8),(byte)initial_data_out_len }));
								
						if ( initial_data_out_len > 0 ){
						
							int	save_pos = initial_data_out.position();
							
							write_cipher.update( initial_data_out, write_buffer );
							
								// reset in case buffer needs to be used again by caller
							
							initial_data_out.position( save_pos );
							
							initial_data_out = null;
						}
						
						write_buffer.flip();
					}
					
					write( write_buffer );
					
					if ( !write_buffer.hasRemaining()){
					
						write_buffer	= null;
					
						protocol_state	= PS_INBOUND_4;
					}
					
				}else if ( protocol_state == PS_INBOUND_3 ){
					
						// B receives: HASH('req1', S), HASH('req2', SKEY)^HASH('req3', S), ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA)
						
					if ( read_buffer == null ){
												
						read_buffer = ByteBuffer.allocate( 20 + PADDING_MAX );
						
						read_buffer.limit( 20 );
						
						SHA1Hasher hasher = new SHA1Hasher();
						
						hasher.update( REQ1_IV );
						hasher.update( secret_bytes );
						
						padding_skip_marker = hasher.getDigest();

						protocol_substate	= 1;
					}					
					
					while( true ){
						
						read( read_buffer );
								
						if ( read_buffer.hasRemaining()){
						
							break;
						}
						
						if ( protocol_substate == 1 ){
							
							 	//skip up to HASH('req1', S)
							
							int	limit = read_buffer.limit();
							
							read_buffer.position( limit - 20 );
							
							boolean match	= true;
							
							for (int i=0;i<20;i++){
								
								if ( read_buffer.get() != padding_skip_marker[i] ){
									
									match	= false;
									
									break;
								}
							}
							
							if ( match ){
							
								read_buffer = ByteBuffer.allocate( 20 + VC.length + 4 + 2 );
								
								protocol_substate	= 2;
								
								break;
							
							}else{
								
								if ( limit == read_buffer.capacity()){
									
									throw( new IOException( "PHE skip to SHA1 marker failed" ));
								}
								
								read_buffer.limit( limit + 1 );
								
								read_buffer.position( limit );
							}
						}else if ( protocol_substate == 2 ){
							
								// find SKEY using HASH('req2', SKEY)^HASH('req3', S)  , ENCRYPT(VC, crypto_provide, len(PadC),
							
							read_buffer.flip();
								
							final byte[]	decode = new byte[20];
							
							read_buffer.get( decode );
							
							SHA1Hasher hasher = new SHA1Hasher();
							
							hasher.update( REQ3_IV );
							hasher.update( secret_bytes );
							
							byte[] sha1 = hasher.getDigest();
							
							for (int i=0;i<decode.length;i++){
								
								decode[i] ^= sha1[i];
							}
							
							synchronized( global_shared_secrets ){
								
								shared_secret	= (byte[])global_shared_secrets.get( new HashWrapper( decode ));
							}
							
							if ( shared_secret == null ){
								
								throw( new IOException( "No matching shared secret" ));
							}
							
							// System.out.println( "inbound - using crypto secret " + ByteFormatter.encodeString( shared_secret ));

							setupCrypto();
							
							byte[]	crypted = new byte[VC.length + 4 + 2];
							
							read_buffer.get( crypted );
							
							byte[]	plain = read_cipher.update( crypted );
							
							byte	other_supported_protocols = plain[VC.length+3];
							
							int	common_protocols = my_supported_protocols & other_supported_protocols;
							
							if (( common_protocols & CRYPTO_PLAIN )!= 0 ){
								
								selected_protocol = CRYPTO_PLAIN;
								
							}else if (( common_protocols & CRYPTO_XOR )!= 0 ){
								
								selected_protocol = CRYPTO_XOR;
								
							}else if (( common_protocols & CRYPTO_RC4 )!= 0 ){
								
								selected_protocol = CRYPTO_RC4;
								
							}else if (( common_protocols & CRYPTO_AES )!= 0 ){
								
								selected_protocol = CRYPTO_AES;
								
							}else{
								
								throw( new IOException( 
										"No crypto protocol in common: mine = " + 
											Integer.toHexString((byte)my_supported_protocols) + ", theirs = " +
											Integer.toHexString((byte)other_supported_protocols)));
				
							}
													
							int	padding	= (( plain[VC.length+4] & 0xff ) << 8 ) + ( plain[VC.length+5] & 0xff );
							
							if ( padding > PADDING_MAX ){
								
								throw( new IOException( "Invalid padding '" + padding + "'" ));
							}

							read_buffer = ByteBuffer.allocate( padding + 2 );
							
								// skip the padding
							
							protocol_substate	= 3;
						
						}else if ( protocol_substate == 3 ){

								// ENCRYPT( len(IA)),  { ENCRYPT(IA) }
							
							read_buffer.flip();
							
							byte[]	data = new byte[read_buffer.remaining()];
							
							read_buffer.get( data );

							data = read_cipher.update( data );
							
							int	ia_len	= 0xffff & ((( data[data.length-2] & 0xff ) << 8 ) + ( data[data.length-1] & 0xff ));

							if ( ia_len > 65535 ){
								
								throw( new IOException( "Invalid IA length '" + ia_len + "'" ));
							}
														
							if ( ia_len > 0 ){
								
								read_buffer = ByteBuffer.allocate( ia_len );
								
								// skip the padding
							
								protocol_substate	= 4;

							}else{
								
								read_buffer	= null;
						        
								protocol_state = PS_OUTBOUND_4;
								
								break;		
							}
						}else if ( protocol_substate == 4 ){

							// ENCRYPT(IA)
						
							read_buffer.flip();
							
							byte[]	data = new byte[read_buffer.remaining()];
							
							read_buffer.get( data );

							data = read_cipher.update( data );
							
								// hack alert - we can delay the writing of the outbound_4 packet if this is an incoming packet with
								// a piggybacked bt handshake as we know that we'll be sending our own handshake back out pretty soon
								// and it'll take the delayed data with it. To be more generic we'd need to add a callback to the pattern
								// matcher to allow it to decide whether delaying was sensible / or stick a timer on the delayed data
							
							delay_outbound_4 = new String( data ).indexOf( "BitTorrent" ) != -1;
							
							// System.out.println( "Initial Data In: " + new String( data ) + "->delay=" +delay_outbound_4 );
							
							initial_data_in = ByteBuffer.wrap( data );
							
							read_buffer	= null;
					        
							protocol_state = PS_OUTBOUND_4;
							
							break;		
						}
					}
				}else if ( protocol_state == PS_OUTBOUND_4 ){
					
						// B->A: ENCRYPT(VC, crypto_select, len(padD), padD, // len(IB)), ENCRYPT(IB)
	
					if ( write_buffer == null ){
								
						int	pad_max = getPaddingMax();
						
						byte[]	padding_b = getRandomPadding( pad_max/2 );	// half padding b sent here

						byte[]	padding_d = getZeroPadding( pad_max );
						
						write_buffer = ByteBuffer.allocate( padding_b.length + VC.length + 4 + 2 + padding_d.length ); // + 2 + initial_data_out.length );
						
						write_buffer.put( padding_b );
						
						write_buffer.put( write_cipher.update( VC ));
						
						write_buffer.put( write_cipher.update( new byte[]{ 0, 0, 0, selected_protocol }));
						
						write_buffer.put( write_cipher.update( new byte[]{ (byte)(padding_d.length>>8),(byte)padding_d.length }));
						
						write_buffer.put( write_cipher.update( padding_d ));
						
						//write_buffer.put( write_cipher.update( new byte[]{ (byte)(initial_data_out.length>>8),(byte)initial_data_out.length }));
											
						//write_buffer.put( write_cipher.update( initial_data_out ));
						
						write_buffer.flip();
					}
					
					if ( delay_outbound_4 ){
						
						if ( transport.delayWrite( write_buffer )){
							
							write_buffer	= null;
							
							handshakeComplete();
							
						}else{
							
							delay_outbound_4 = false;
						}
					}
					
					if ( !delay_outbound_4 ){
						
						write( write_buffer );
						
						if ( !write_buffer.hasRemaining()){
						
							write_buffer	= null;
						
							handshakeComplete();
						}
					}
				}else if ( protocol_state == PS_INBOUND_4 ){
					
						// B->A: ENCRYPT(VC, crypto_select, len(padD), padD // , len(IB)), ENCRYPT(IB)
					
					if ( read_buffer == null ){
												
						read_buffer = ByteBuffer.allocate( VC.length + PADDING_MAX );
						
						read_buffer.limit( VC.length );
								
						padding_skip_marker	= new byte[VC.length];
						
						padding_skip_marker	= read_cipher.update( padding_skip_marker );
							
						protocol_substate	= 1;
					}					
				
					while( true ){
											
						read( read_buffer );
						
						if ( read_buffer.hasRemaining()){
						
							break;
						}
						
						if ( protocol_substate == 1 ){
							
							 	//skip up to marker
							
							int	limit = read_buffer.limit();
							
							read_buffer.position( limit - VC.length );
							
							boolean match	= true;
							
							for (int i=0;i<VC.length;i++){
								
								if ( read_buffer.get() != padding_skip_marker[i] ){
									
									match	= false;
									
									break;
								}
							}
							
							if ( match ){
							
								read_buffer = ByteBuffer.allocate( 4 + 2 );
								
								protocol_substate	= 2;
								
								break;
							
							}else{
								
								if ( limit == read_buffer.capacity()){
									
									throw( new IOException( "PHE skip to SHA1 marker failed" ));
								}
								
								read_buffer.limit( limit + 1 );
								
								read_buffer.position( limit );
							}
						}else if ( protocol_substate == 2 ){
							
								//  ENCRYPT( crypto_select, len(padD))
							
							read_buffer.flip();
								
							byte[]	crypted = new byte[4 + 2];
							
							read_buffer.get( crypted );
							
							byte[]	plain = read_cipher.update( crypted );
							
							selected_protocol = plain[3];
							
							if (( selected_protocol & my_supported_protocols ) == 0 ){
								
								
								throw( new IOException( 
										"Selected protocol has nothing in common: mine = " + 
											Integer.toHexString((byte)my_supported_protocols) + ", theirs = " +
											Integer.toHexString((byte)selected_protocol)));
				
							}
													
							int	pad_len	= 0xffff&((( plain[4] & 0xff ) << 8 ) + ( plain[5] & 0xff ));
							
							if ( pad_len > 65535 ){
								
								throw( new IOException( "Invalid pad length '" + pad_len + "'" ));
							}
	
							read_buffer = ByteBuffer.allocate( pad_len ); // + 2 );
									
							protocol_substate	= 3;
														
						}else if ( protocol_substate == 3 ){
						
							read_buffer.flip();
							
							byte[]	data = new byte[read_buffer.remaining()];
							
							read_buffer.get( data );

							data = read_cipher.update( data );
							
							handshakeComplete();
							
							read_buffer	= null;
				        
							break;
							/*
							int	ib_len	= 0xffff & ((( data[data.length-2] & 0xff ) << 8 ) + ( data[data.length-1] & 0xff ));

							if ( ib_len > 65535 ){
								
								throw( new IOException( "Invalid IB length '" + ib_len + "'" ));
							}
							
							read_buffer = ByteBuffer.allocate( ib_len );
							
							protocol_substate	= 4;
							
						}else{

							read_buffer.flip();
							
							byte[]	data = new byte[read_buffer.remaining()];
							
							read_buffer.get( data );

							initial_data_in = read_cipher.update( data );		
							
							handshakeComplete();
							
							read_buffer	= null;
				        
							break;
							*/
						}
					}
				}
		
				if ( handshake_complete ){
					
					transport.cancelReadSelects();
					
					transport.cancelWriteSelects();
					
					loop	= false;
					
					complete();
					
				}else{
				
					if ( read_buffer == null ){
						
						transport.pauseReadSelects();
						
					}else{
						
						transport.resumeReadSelects();
						
						loop	= false;
						
					}
					
					if ( write_buffer == null ){
						
						transport.pauseWriteSelects();
						
					}else{
						
						transport.resumeWriteSelects();
						
						loop	= false;
					}
				}
			}
		}catch( Throwable e ){
						
			failed( e );
			
			if ( e instanceof IOException ){
				
				throw((IOException)e);
				
			}else{
				
				throw( new IOException( Debug.getNestedExceptionMessage(e)));
			}
		}finally{
			
			process_mon.exit();
		}
	
protected voidread(java.nio.ByteBuffer buffer)

		int	len = transport.read( buffer );
	
		//System.out.println( "read:" + this + "/" + protocol_state + "/" + protocol_substate + " -> " + len +"[" + buffer +"]");
		
		if ( len < 0 ){
			
			throw( new IOException( "end of stream on socket read - phe: " + getString()));
		}
		
		bytes_read += len;
	
public static voidremoveSecretsSupport(byte[][] secrets)

		for (int i=0;i<secrets.length;i++){

			SHA1Hasher hasher = new SHA1Hasher();
	   		
	   		hasher.update( REQ2_IV );
	   		hasher.update( secrets[i] );
	   		
	   		byte[]	encoded = hasher.getDigest();
			                  	
			synchronized( global_shared_secrets ){
				
				global_shared_secrets.remove( new HashWrapper( encoded ));
			}
		}
	
public voidselectFailure(TransportHelper transport, java.lang.Object attachment, java.lang.Throwable msg)

		failed( msg );
	
public booleanselectSuccess(TransportHelper transport, java.lang.Object attachment, boolean write_operation)

		try{
			int	old_bytes_read		= bytes_read;
			int	old_bytes_written	= bytes_written;
			
			process();
			
			if ( write_operation ){
				
				return( bytes_written != old_bytes_written );
				
			}else{
				
				boolean	progress = bytes_read != old_bytes_read;
				
				if ( progress ){
					
					last_read_time = SystemTime.getCurrentTime();
				}
				
				return( progress );
			}
			
		}catch( Throwable  e ){
			
			failed( e );
			
			return( false );
		}
	
protected voidsetupCrypto()

		try{
		    //"HASH('keyA', S, SKEY)" if you're A
		    //"HASH('keyB', S, SKEY)" if you're B

		    SHA1Hasher	hasher = new SHA1Hasher();
		    
		    hasher.update( KEYA_IV );
		    hasher.update( secret_bytes );
		    hasher.update( shared_secret );
		    	
		    byte[]	a_key = hasher.getDigest();
		    
		    hasher = new SHA1Hasher();
		    
		    hasher.update( KEYB_IV );
		    hasher.update( secret_bytes );
		    hasher.update( shared_secret );
		    	
		    byte[]	b_key = hasher.getDigest();
		    
		    SecretKeySpec	secret_key_spec_a = new SecretKeySpec( a_key, RC4_STREAM_ALG );
		        
		    SecretKeySpec	secret_key_spec_b = new SecretKeySpec( b_key, RC4_STREAM_ALG );
		        	        
		    write_cipher 	= new TransportCipher( RC4_STREAM_CIPHER, Cipher.ENCRYPT_MODE, outbound?secret_key_spec_a:secret_key_spec_b );
			    
		    read_cipher 	= new TransportCipher( RC4_STREAM_CIPHER, Cipher.DECRYPT_MODE, outbound?secret_key_spec_b:secret_key_spec_a );
		    
		}catch( Throwable e ){
			
			e.printStackTrace();
			
			throw( new IOException( Debug.getNestedExceptionMessage(e)));
		}
	
protected voidwrite(java.nio.ByteBuffer buffer)

		//System.out.println( "write pre:" + this + "/" + protocol_state + "/" + protocol_substate + " - " + buffer );

		int	len = transport.write( buffer, false );
		
		//System.out.println( "write:" + this + "/" + protocol_state + "/" + protocol_substate + " -> " + len +"[" + buffer +"]");

		if ( len < 0 ){
			
			throw( new IOException( "bytes written < 0 " ));			
		}
		
		bytes_written += len;