Package com.aelitis.azureus.core.networkmanager.impl

Source Code of com.aelitis.azureus.core.networkmanager.impl.ProtocolDecoderPHE

/*
* Created on 17-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.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.*;

import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPublicKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.logging.LogAlert;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.ByteFormatter;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.HashWrapper;
import org.gudy.azureus2.core3.util.LightHashMap;
import org.gudy.azureus2.core3.util.RandomUtils;
import org.gudy.azureus2.core3.util.SHA1Hasher;
import org.gudy.azureus2.core3.util.SystemTime;

import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.util.bloom.BloomFilter;
import com.aelitis.azureus.core.util.bloom.BloomFilterFactory;

public class
ProtocolDecoderPHE
  extends ProtocolDecoder
{
  private static final LogIDs LOGID = LogIDs.NWMAN;

  private static final byte    CRYPTO_PLAIN  = 0x01;
  private static final byte    CRYPTO_RC4    = 0x02;
  private static final byte    CRYPTO_XOR    = 0x04;
  private static final byte    CRYPTO_AES    = 0x08;

 
  //private static final String   DH_P = "92d862b3a95bff4e6cbdce3a266ff4b46e6e1ecad76c0a877d92a3dae4999e6414efde56fc14d1cca6d5408a8ef9ea248389168876b6e8f4503845dfe373549f";
  //private static final String   DH_G = "4383b53ee650fd73e41e8c9e8527997ab8cb41e1cbd73ac7685493e1e5d091e3e3789dea03ab9d5b2c368faa617bb30e427cbaeb23c268edb38eb8c747756080";
  // private static final String   DH_P = "f3f90c790c63b119f9c1be43fdb12dc6ed6f26325999c01ba6ed373e75d6b2dee8d1c0475652a987c8df57b23d395bdb142be316d780b9361f85629535030873";
 
  private static final String   DH_P = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A36210000000000090563";
  private static final String   DH_G = "02";
  private static final int    DH_L = 160;
 
  private static final int    DH_SIZE_BYTES  = DH_P.length()/2;

  public static final int MIN_INCOMING_INITIAL_PACKET_SIZE  = DH_SIZE_BYTES;
 

  private static final BigInteger  DH_P_BI = new BigInteger( DH_P, 16 );
  private static final BigInteger  DH_G_BI = new BigInteger( DH_G, 16 );
 
  private static KeyPairGenerator    dh_key_generator;
  private static long          last_dh_incoming_key_generate;
 
  private static final int      BLOOM_RECREATE        = 30*1000;
  private static final int      BLOOM_INCREASE        = 1000;
  private static BloomFilter      generate_bloom        = BloomFilterFactory.createAddRemove4Bit(BLOOM_INCREASE);
  private static long          generate_bloom_create_time  = SystemTime.getCurrentTime();
 
  private static boolean  crypto_setup_done;
  private static boolean  crypto_ok;
  //private static boolean  aes_ok;
 
  /*
  private static final String    AES_STREAM_ALG        = "AES";
  private static final String    AES_STREAM_CIPHER      = "AES/CFB8/NoPadding";
  private static final int    AES_STREAM_KEY_SIZE      = 128;
  private static final int    AES_STREAM_KEY_SIZE_BYTES  = AES_STREAM_KEY_SIZE/8;
     */
 
  //private static final byte[]    AES_STREAM_IV        =
    //  {   (byte)0x15, (byte)0xE0, (byte)0x6B, (byte)0x7E, (byte)0x98, (byte)0x59, (byte)0xE4, (byte)0xA7,
    //    (byte)0x34, (byte)0x66, (byte)0xAD, (byte)0x48, (byte)0x35, (byte)0xE2, (byte)0xD0, (byte)0x24 };
   
   
  private static final String    RC4_STREAM_ALG        = "RC4";
  private static final String    RC4_STREAM_CIPHER      = "RC4";
  private static final int    RC4_STREAM_KEY_SIZE      = 128;
  private static final int    RC4_STREAM_KEY_SIZE_BYTES  = RC4_STREAM_KEY_SIZE/8;
   
   
    private static final int    PADDING_MAX  = 512;
   
    private static final int    PADDING_MAX_NORMAL  = PADDING_MAX;
    private static final int    PADDING_MAX_LIMITED  = 128;
      
    public static int
    getMaxIncomingInitialPacketSize(
      boolean  min_overheads )
    {
      return( MIN_INCOMING_INITIAL_PACKET_SIZE + (min_overheads?PADDING_MAX_LIMITED:PADDING_MAX_NORMAL)/2 );
    }
     
    private static Random  random = RandomUtils.SECURE_RANDOM;
   
  private static Map  global_shared_secrets  = new LightHashMap();
 
  private static void
  cryptoSetup()
  {
    synchronized( global_shared_secrets ){
     
      if ( crypto_setup_done ){
       
        return;
      }
     
      crypto_setup_done = true;
     
      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.lognew LogEvent(LOGID, "PHE crypto initialised" ));
           }
      }catch( NoClassDefFoundError e ){
       
          // running without PHE classes, not such a severe error
         
        Logger.lognew LogEvent(LOGID, "PHE crypto disabled as classes unavailable" ));
       
        crypto_ok  = false;
       
      }catch( Throwable e ){
                  
            Logger.lognew LogEvent(LOGID, "PHE crypto initialisation failed", e ));
       
        crypto_ok  = false;
      }
    }
  }
 
  public static boolean
  isCryptoOK()
  {
    cryptoSetup();
   
    return( crypto_ok );
  }
 
  public static void
  addSecretsSupport(
    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] );
      }
    }
  }
 
  public static void
  removeSecretsSupport(
    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 ));
      }
    }
  }
 
  // private static final byte SUPPORTED_PROTOCOLS = (byte)((aes_ok?CRYPTO_AES:0) | CRYPTO_RC4 | CRYPTO_XOR | CRYPTO_PLAIN );
  private static final byte SUPPORTED_PROTOCOLS = (byte)(CRYPTO_RC4 | CRYPTO_PLAIN );

 
  private static byte   MIN_CRYPTO;
 
  static{
      COConfigurationManager.addAndFireParameterListeners(
          new String[]{ "network.transport.encrypted.min_level" },
          new ParameterListener()
          {
             public void
             parameterChanged(
              String ignore )
             {
               if ( NetworkManager.REQUIRE_CRYPTO_HANDSHAKE && !isCryptoOK() ){                
                 Logger.log( new LogAlert(true,LogAlert.AT_ERROR,"Connection encryption unavailable, please update your Java version" ));
               }
              
               String  min  = COConfigurationManager.getStringParameter( "network.transport.encrypted.min_level");
              
               if ( min.equals( "XOR" )){
                
                 MIN_CRYPTO  = CRYPTO_XOR | CRYPTO_RC4 | CRYPTO_AES;
                
               }else if ( min.equals( "RC4" )){
                
                 MIN_CRYPTO  = CRYPTO_RC4 | CRYPTO_AES;
                
               }else if ( min.equals( "AES" )){
               
                 MIN_CRYPTO  = CRYPTO_AES;
                
               }else{
                
                 MIN_CRYPTO  = CRYPTO_PLAIN | CRYPTO_XOR | CRYPTO_RC4 | CRYPTO_AES;
               }
              
               MIN_CRYPTO = (byte)(MIN_CRYPTO & SUPPORTED_PROTOCOLS);
             }
          });
  }
 
   
  private static final int    PS_OUTBOUND_1  = 0;
  private static final int    PS_OUTBOUND_2  = 1;
  private static final int    PS_OUTBOUND_3  = 2;
  private static final int    PS_OUTBOUND_4  = 3;
 
  private static final int    PS_INBOUND_1  = 10;
  private static final int    PS_INBOUND_2  = 11;
  private static final int    PS_INBOUND_3  = 12;
  private static final int    PS_INBOUND_4  = 13;

  public static final byte[]  KEYA_IV  = "keyA".getBytes();
  public static final byte[]  KEYB_IV  = "keyB".getBytes();
  public static final byte[]  REQ1_IV  = "req1".getBytes();
  public static final byte[]  REQ2_IV  = "req2".getBytes();
  public static final byte[]  REQ3_IV  = "req3".getBytes();
  public static final byte[]  VC    = { 0,0,0,0,0,0,0,0};
 
 
 
  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  = SystemTime.getCurrentTime();
 
  private TransportHelperFilter    filter;
 
  private boolean      delay_outbound_4;

  private boolean processing_complete;
 
  private AEMonitor  process_mon  = new AEMonitor( "ProtocolDecoderPHE:process" );
 
  public
  ProtocolDecoderPHE(
    TransportHelper        _transport,
    byte[][]          _shared_secrets,
    ByteBuffer          _header,
    ByteBuffer          _initial_data,
    ProtocolDecoderAdapter    _adapter )
 
    throws IOException
  {
    super( false );
   
    if ( !isCryptoOK()){
     
      throw( new IOException( "PHE crypto broken" ));
    }
   
    transport      = _transport;
    // scattering mode for the first KB, that should include the crypto handshake and part of the bittorrent handshake
    transport.setScatteringMode(768+random.nextInt(256));
    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();
  }
 
  protected void
  initCrypto()
 
    throws IOException
  {
    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)));
    }
  }
 
  protected void
  completeDH(
    byte[]  buffer )
 
    throws IOException
  {
    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 void
  setupCrypto()
 
    throws IOException
  {
    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 void
  completeDH(
    byte[]  buffer )
 
    throws IOException
  {
    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 );
       
        byte[]  secret_bytes_64 = key_agreement.generateSecret();
 
          // we only want the first 32 bytes of the secret
       
        secret_bytes = new byte[32];
       
        System.arraycopy( secret_bytes_64, 0, secret_bytes, 0, 32 );
       
        sha1_secret_bytes  = new SHA1Simple().calculateHash( secret_bytes );
                 
        SecretKeySpec  secret_key_spec_a = new SecretKeySpec( secret_bytes, 0, RC4_STREAM_KEY_SIZE_BYTES, RC4_STREAM_ALG );
           
        SecretKeySpec  secret_key_spec_b = new SecretKeySpec( secret_bytes, 16, RC4_STREAM_KEY_SIZE_BYTES, RC4_STREAM_ALG );
                     
        write_cipher   = new TCPTransportCipher( RC4_STREAM_CIPHER, Cipher.ENCRYPT_MODE, outbound?secret_key_spec_a:secret_key_spec_b );
         
        read_cipher   = new TCPTransportCipher( RC4_STREAM_CIPHER, Cipher.DECRYPT_MODE, outbound?secret_key_spec_b:secret_key_spec_a );
       
    }catch( Throwable e ){
     
      throw( new IOException( Debug.getNestedExceptionMessage(e)));
    }
  }
  */
 
  protected void
  handshakeComplete()
 
    throws IOException
  {   
    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 TransportHelperFilterInserterfilter, initial_data_in );
    }
   
    handshake_complete  = true;
  }
 
 
  /*
        X_1
       
      A->B: Diffie Hellman Ya, PadA
     
        X_2
       
    B->A: Diffie Hellman Yb, PadB
   
      X_3
     
    A->B: HASH('req1', S), HASH('req2', SKEY)^HASH('req3', S), ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA)
   
      X_4
     
    B->A: ENCRYPT(VC, crypto_select, len(padD), padD ) // , len(IB)), ENCRYPT(IB)
  */

 
  protected void
  process()
 
    throws IOException
  {
    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();
    }
  }
 
 
 
 
  /*
      **** OUTBOUND_1
     
    A sends B odd/even byte + Ya + Pa
   
      **** INBOUND_1
     
    B receives Ya
    B computes Yb
    B computes S and HS
   
    **** OUTBOUND_2
   
    B sends A Yb + HS( "supported methods" + len(Pb)) + Pb
   
      **** INBOUND_2
     
    A receives Yb
    A computes S and HS
    A receives HS( "supported methods" + len(Pb)) and decrypts using HS
    A skips len(Pb) random bytes
   
    **** OUTBOUND_3
   
    A sends SHA1(S) + HS( "selected method" + len(Pc)) + Pc + selectedCrypt( payload )
   
      **** INBOUND_3
     
    B skips Pa bytes until receives SHA1(S)
    B decrypts "selected method" + len(Pc) and skips len(Pc) bytes to get to selectedCrypt( payload... )
    B sends A selectedCrypt( payload... )
   */
 
 
 
 
  /*
  protected void
  process()
 
    throws IOException
  {
    try{
      process_mon.enter();
     
      if ( handshake_complete ){
       
        Debug.out( "Handshake process already completed" );
       
        return;
      }
     
      boolean  loop = true;
   
      while( loop ){
         
        if ( protocol_state == PS_OUTBOUND_1 ){
         
          if ( write_buffer == null ){
           
              // A sends B odd/even Ya + Pa
           
           
            byte[]  padding = getPadding();
                       
            write_buffer = ByteBuffer.allocate( dh_public_key_bytes.length + padding.length );
                       
            write_buffer.put( dh_public_key_bytes );
           
            write_buffer.put( padding );
           
            write_buffer.flip();
          }
         
          write( write_buffer );
           
          if ( !write_buffer.hasRemaining()){
         
            write_buffer  = null;
         
            protocol_state  = PS_INBOUND_2;
          }
         
        }else if ( protocol_state == PS_OUTBOUND_2 ){
         
            // B sends A Yb + HS( "supported methods" + len(Pb)) + Pb
   
          if ( write_buffer == null ){
           
            byte[]  padding = getPadding();
           
            write_buffer = ByteBuffer.allocate( dh_public_key_bytes.length + 4 + 2 + padding.length );
           
            write_buffer.put( dh_public_key_bytes );
                           
              // 4 bytes for my supported protocols
                     
            write_buffer.put( write_cipher.update( new byte[]{ 0, 0, 0, my_supported_protocols }));
           
            write_buffer.put( write_cipher.update( new byte[]{ (byte)(padding.length>>8),(byte)padding.length }));
         
            write_buffer.put( padding );
           
            write_buffer.flip();
          }
         
          write( write_buffer );
         
          if ( !write_buffer.hasRemaining()){
         
            write_buffer  = null;
         
            protocol_state  = PS_INBOUND_3;
          }
         
        }else if ( protocol_state == PS_OUTBOUND_3 ){
         
            //   A sends SHA1(S) + HS( "selected method" + len(Pc)) + Pc + selectedCrypt( payload )
   
          if ( write_buffer == null ){
           
            byte[]  padding = getPadding();
           
            write_buffer = ByteBuffer.allocate( 20 + 4 + 2 + padding.length );
           
            write_buffer.put( sha1_secret_bytes );
                           
            write_buffer.put( write_cipher.update( new byte[]{ 0, 0, 0, selected_protocol }));
           
            write_buffer.put( write_cipher.update( new byte[]{ (byte)(padding.length>>8),(byte)padding.length }));
         
            write_buffer.put( padding );
           
            write_buffer.flip();
          }
         
          write( write_buffer );
         
          if ( !write_buffer.hasRemaining()){
         
            write_buffer  = null;
         
            handshakeComplete();
          }
         
        }else if ( protocol_state == PS_INBOUND_1 ){
             
            // B receives marker + 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_INBOUND_2 ){
           
          //A receives Yb
          //A computes S and HS
          //A receives HS( "supported methods" + len(Pb)) and decrypts using HS
          //A skips len(Pb) random bytes
           
          if ( read_buffer == null ){
                       
            read_buffer = ByteBuffer.allocate( dh_public_key_bytes.length + 6 );
           
            protocol_substate  = 1;
          }         
           
          while( true ){
           
            read( read_buffer );
               
            if ( read_buffer.hasRemaining()){
             
              break;
            }
           
            if ( protocol_substate == 1 ){
             
              read_buffer.flip();
             
              byte[] other_dh_public_key_bytes_etc = read_buffer.array();
     
              completeDH( other_dh_public_key_bytes_etc );
     
              byte[]  etc = read_cipher.update( other_dh_public_key_bytes_etc, DH_SIZE_BYTES, 6 );
             
              byte  other_supported_protocols = etc[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  = (( etc[4] & 0xff ) << 8 ) + ( etc[5] & 0xff );
             
              if ( padding > PADDING_MAX ){
               
                throw( new IOException( "Invalid padding '" + padding + "'" ));
              }
             
              read_buffer = ByteBuffer.allocate( padding );
             
              protocol_substate  = 2;
             
            }else{
             
              read_buffer  = null;
                           
              protocol_state  = PS_OUTBOUND_3;
             
              break;
            }
          }
        }else if ( protocol_state == PS_INBOUND_3 ){
         
         
          //  B skips Pa bytes until receives SHA1(S)
          //  B decrypts "selected method" + len(Pc) and skips len(Pc) bytes
           
          if ( read_buffer == null ){
                       
            read_buffer = ByteBuffer.allocate( 20 + PADDING_MAX );
           
            read_buffer.limit( 20 );
           
            protocol_substate  = 1;
          }         
         
          while( true ){
           
            read( read_buffer );
               
            if ( read_buffer.hasRemaining()){
           
              break;
            }
           
            if ( protocol_substate == 1 ){
             
              int  limit = read_buffer.limit();
             
              read_buffer.position( limit - 20 );
             
              boolean match  = true;
             
              for (int i=0;i<20;i++){
               
                if ( read_buffer.get() != sha1_secret_bytes[i] ){
                 
                  match  = false;
                 
                  break;
                }
              }
             
              if ( match ){
             
                read_buffer = ByteBuffer.allocate( 6 );
               
                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 ){
             
              read_buffer.flip();
               
              byte[]  etc = read_cipher.update( read_buffer.array());
             
              selected_protocol = etc[3];
             
              int  padding  = (( etc[4] & 0xff ) << 8 ) + ( etc[5] & 0xff );
             
              if ( padding > PADDING_MAX ){
               
                throw( new IOException( "Invalid padding '" + padding + "'" ));
              }

              read_buffer = ByteBuffer.allocate( padding );
             
              protocol_substate  = 3;
                           
            }else{
             
              read_buffer  = null;
               
              handshakeComplete();
             
              break;
            }
          }
        }
   
        if ( handshake_complete ){
         
          read_selector.cancel( channel );
         
          write_selector.cancel( channel );
         
          loop  = false;
         
          complete();
         
        }else{
       
          if ( read_buffer == null ){
           
            read_selector.pauseSelects( channel );
           
          }else{
           
            read_selector.resumeSelects ( channel );
           
            loop  = false;
           
          }
         
          if ( write_buffer == null ){
           
            write_selector.pauseSelects( channel );
           
          }else{
           
            write_selector.resumeSelects ( channel );
           
            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 void
  read(
    ByteBuffer    buffer )
 
    throws IOException
  {
    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;
  }
 
  protected void
  write(
    ByteBuffer    buffer )
 
    throws IOException
  {
    //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;
  }
 
  public boolean
  selectSuccess(
    TransportHelper      transport,
    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 );
    }
  }

  public void
  selectFailure(
    TransportHelper      transport,
    Object           attachment,
    Throwable        msg )
  {
    failed( msg );
  }
 
  protected byte[]
  bigIntegerToBytes(
    BigInteger  bi,
    int      num_bytes )
  {
    String  str = bi.toString(16);
   
    while( str.length() < num_bytes*2 ){
      str = "0" + str;
    }
   
    return( ByteFormatter.decodeString(str));
  }
 
  protected BigInteger
  bytesToBigInteger(
    byte[]  bytes,
    int    offset,
    int    len )
  {   
    return( new BigInteger( ByteFormatter.encodeString( bytes, offset, len  ), 16 ));
  }
 
  protected int
  getPaddingMax()
  {
    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 );
  }
 
  protected static synchronized byte[]
     getZeroPadding(
       int  max_len )
     {
       byte[]  bytes = new byte[ random.nextInt(max_len)];
      
       return( bytes );
     }
 
  protected static KeyPair
  generateDHKeyPair(
    TransportHelper    transport,
    boolean        outbound )
 
    throws IOException
  {
    if ( dh_key_generator == null ){
     
      throw( new IOException( "Crypto not setup" ));
    }
   
    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.lognew 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.lognew 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 );
    }
  }
 
  protected void
  complete()
  {
    // 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;
   
    transport.setScatteringMode(0);
   
    adapter.decodeComplete( this, initial_data_out )
  }
 
  protected void
  failed(
    Throwable   cause )
  {
    // System.out.println( (outbound?"out: ":"in :") + " failed, " + cause.getMessage());

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

    adapter.decodeFailed( this, cause );
  }
 
  public boolean
  isComplete(
    long    now )
  {
    return( processing_complete );
  }
 
  public TransportHelperFilter
  getFilter()
  {
    return( filter );
  }

  public long
  getLastReadTime()
  {
    long  now = SystemTime.getCurrentTime();
   
    if ( last_read_time > now ){
     
      last_read_time  = now;
    }
   
    return( last_read_time );
  }
 
  public String
  getString()
  {
    return( "state=" + protocol_state + ",sub=" + protocol_substate + ",in=" + bytes_read + ",out=" + bytes_written);
  }
}
TOP

Related Classes of com.aelitis.azureus.core.networkmanager.impl.ProtocolDecoderPHE

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.