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

Source Code of com.aelitis.azureus.core.networkmanager.impl.udp.UDPConnectionSet$SequenceGenerator

/*
* Created on 22 Jun 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.udp;

import java.util.*;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;

import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.RC4Engine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.SHA1Hasher;
import org.gudy.azureus2.core3.util.SystemTime;

import com.aelitis.net.udp.uc.PRUDPPacketReply;

public class
UDPConnectionSet
{
  private static final LogIDs LOGID = LogIDs.NET;
 
  private static final boolean  DEBUG_SEQUENCES  = false;
 

  static{
    if ( DEBUG_SEQUENCES ){
      System.out.println( "**** UDPConnectionSet: debug sequences is on ****" );
    }
  }
 
  private static final byte[]  KEYA_IV  = "UDPDriverKeyA".getBytes();
  private static final byte[]  KEYB_IV  = "UDPDriverKeyB".getBytes();
  private static final byte[]  KEYC_IV  = "UDPDriverKeyC".getBytes();
  private static final byte[]  KEYD_IV  = "UDPDriverKeyD".getBytes();
 
  private static final int MIN_MSS  = 256;
  private static final int MAX_HEADER  = 128;
 
  public static final int MIN_WRITE_PAYLOAD    = MIN_MSS - MAX_HEADER;
  public static final int MAX_BUFFERED_PAYLOAD  = 512;
   
  private UDPConnectionManager  manager;
  private UDPSelector        selector;
  private int            local_port;
  private InetSocketAddress    remote_address;
  private boolean          outgoing;
  private String          connection_key;
 
  private Random      random;
 
  private UDPConnection  lead_connection;
   
  private RC4Engine    header_cipher_out;
  private RC4Engine    header_cipher_in;
 
  private SequenceGenerator  in_seq_generator;
  private SequenceGenerator  out_seq_generator;

 
  private volatile boolean  crypto_done;
 
  private volatile boolean  failed;
 
  private Map  connections = new HashMap();
 
  private LinkedList  connection_writers = new LinkedList();
 
  private long  total_tick_count;
 
  private static final int STATS_LOG_TIMER  = 60*1000;
  private static final int STATS_LOG_TICKS  = Math.max( 1, STATS_LOG_TIMER / UDPConnectionManager.TIMER_TICK_MILLIS );
  private int stats_log_ticks = STATS_LOG_TICKS;

 
 
  private static final int IDLE_TIMER  = 10*1000;
  private static final int IDLE_TICKS  = Math.max( 1, IDLE_TIMER / UDPConnectionManager.TIMER_TICK_MILLIS );
  private int idle_ticks = 0;

  private static final int  TIMER_BASE_DEFAULT    = 300;
  private static final int  TIMER_BASE_MIN      = 100;
  private static final int  TIMER_BASE_MAX      = 15*1000;
 
  private int  current_timer_base  = TIMER_BASE_DEFAULT;
  private int old_timer_base    = current_timer_base;
  private boolean  timer_is_adjusting;


    // these stats_ values are reset periodically
 
  private int  stats_packets_unique_sent;      // unique packets sent
  private int stats_packets_resent_via_timer;    // total resent due to resend timer expiry
  private int  stats_packets_unique_received;    // unique packets received (not resends)
  private int stats_packets_duplicates;      // duplicates received
 
  private static final int STATS_RESET_TIMER = 30*1000;
 
  private long  stats_reset_time = SystemTime.getCurrentTime();
 
    // cumulative stats
 
  private int  total_packets_sent    = 0;
  private int  total_data_sent      = 0;
  private int  total_data_resent    = 0;
  private int  total_protocol_sent    = 0;
  private int  total_protocol_resent  = 0;
 
  private int total_packets_unique_sent    = 0;
  private int total_packets_received      = 0;
  private int total_packets_unique_received  = 0;
  private int total_packets_duplicates    = 0;
  private int total_packets_out_of_order    = 0;
  private int total_packets_resent_via_timer  = 0;
  private int total_packets_resent_via_ack  = 0;


 
    // transmit


 
  private int retransmit_ticks = 0;
  private UDPPacket  current_retransmit_target;
 
  private static final int RETRANSMIT_COUNT_LIMIT  = 5;

 
  private static final int MIN_RETRANSMIT_TIMER  = 100;
  private static final int MIN_RETRANSMIT_TICKS  = Math.max( 1, MIN_RETRANSMIT_TIMER / UDPConnectionManager.TIMER_TICK_MILLIS );
  private static final int MAX_RETRANSMIT_TIMER  = 20*1000;
  private static final int MAX_RETRANSMIT_TICKS  = Math.max( 1, MAX_RETRANSMIT_TIMER / UDPConnectionManager.TIMER_TICK_MILLIS );

 
  private static final int   MAX_TRANSMIT_UNACK_DATA_PACKETS  = 10;
  private static final int   MAX_TRANSMIT_UNACK_PACKETS    = MAX_TRANSMIT_UNACK_DATA_PACKETS + 4// + protocol packets
  private List        transmit_unack_packets = new ArrayList();
   
  private static final int  MAX_CONTIGUOUS_RETRANS_FOR_ACK  = 3;
 
  private static final int MIN_KEEPALIVE_TIMER  = 10*1000;
  private static final int MIN_KEEPALIVE_TICKS  = Math.max( 1, MIN_KEEPALIVE_TIMER / UDPConnectionManager.TIMER_TICK_MILLIS );
  private static final int MAX_KEEPALIVE_TIMER  = 20*1000;
  private static final int MAX_KEEPALIVE_TICKS  = Math.max( 1, MAX_KEEPALIVE_TIMER / UDPConnectionManager.TIMER_TICK_MILLIS );
  private int  keep_alive_ticks;
   
    // receive
   
  private int    receive_last_inorder_sequence    = -1;
  private int    receive_last_inorder_alt_sequence  = -1;
 
  private int    receive_their_last_inorder_sequence    = -1;

  private static final int RECEIVE_UNACK_IN_SEQUENCE_LIMIT  = 3;
  private long  current_receive_unack_in_sequence_count  = 0;
  private long  sent_receive_unack_in_sequence_count  = 0;
 
  private static final int RECEIVE_OUT_OF_ORDER_ACK_LIMIT    = 3;
  private long  current_receive_out_of_order_count    = 0;
  private long  sent_receive_out_of_order_count      = 0;
 
 
  private static final int RECEIVE_DONE_SEQ_MAX  = 128;
  private LinkedList  receive_done_sequences  = new LinkedList();
 
  private static final int RECEIVE_OUT_OF_ORDER_PACKETS_MAX  = 64;
  private List  receive_out_of_order_packets  = new LinkedList();
 
  private int explicitack_ticks = 0;

  private static final int MAX_SEQ_MEMORY = Math.max( RECEIVE_OUT_OF_ORDER_PACKETS_MAX, MAX_TRANSMIT_UNACK_PACKETS );
 
  protected
  UDPConnectionSet(
    UDPConnectionManager  _manager,
    String          _connection_key,
    UDPSelector        _selector,
    int            _local_port,
    InetSocketAddress    _remote_address )
  {
    manager      = _manager;
    connection_key  = _connection_key;
    selector    = _selector;
    local_port    = _local_port;
    remote_address  = _remote_address;
  }
 
  protected UDPSelector
  getSelector()
  {
    return( selector );
  }
 
  protected InetSocketAddress
  getRemoteAddress()
  {
    return( remote_address );
  }
 
  protected String
  getKey()
  {
    return( connection_key );
  }
 
  protected void
  add(
    UDPConnection  connection )
 
    throws IOException
  {
    UDPConnection  old_connection = null;
   
    synchronized( connections ){
     
      if ( failed ){
       
        throw( new IOException( "Connection set has failed" ));
      }
     
      old_connection =  (UDPConnection)connections.put( new Integer( connection.getID()), connection );
     
      if ( connections.size() == 1 && lead_connection == null ){
       
        lead_connection = connection;
       
        outgoing    = true;
      }
    }
   
    if ( old_connection != null ){
     
      Debug.out( "Duplicate connection" );
     
      old_connection.close( "Duplication connection" );
    }
  }
 
  protected boolean
  remove(
    UDPConnection  connection )
  {
    synchronized( connections ){
 
      connections.remove( new Integer( connection.getID()));
     
      return( connections.size() == 0 );
    }
  }
 
  protected void
  poll()
  {
    synchronized( connections ){

      Iterator  it = connections.values().iterator();
     
      while( it.hasNext()){
       
        ((UDPConnection)it.next()).poll();
      }
    }
  }
 
  protected void
  setSecret(
    UDPConnection  connection,
    byte[]      session_secret )
  {
    try{
      if ( connection == lead_connection ){
         
        if ( manager.trace() ){
          trace( "crypto done" );
        }
       
          SHA1Hasher  hasher = new SHA1Hasher();
         
          hasher.update( KEYA_IV );
          hasher.update( session_secret );
           
          byte[]  a_key = hasher.getDigest();
         
          hasher = new SHA1Hasher();
         
          hasher.update( KEYB_IV );
          hasher.update( session_secret );
           
          byte[]  b_key = hasher.getDigest();
         
          hasher = new SHA1Hasher();
         
          hasher.update( KEYC_IV );
          hasher.update( session_secret );
           
          byte[]  c_key = hasher.getDigest();
 
          hasher = new SHA1Hasher();
         
          hasher.update( KEYD_IV );
          hasher.update( session_secret );
           
          byte[]  d_key = hasher.getDigest();
           
            // for RC4 enc/dec is irrelevant
         
          RC4Engine rc4_engine_a  = getCipher( a_key )
          RC4Engine rc4_engine_b  = getCipher( b_key );
          RC4Engine rc4_engine_c  = getCipher( c_key )
          RC4Engine rc4_engine_d  = getCipher( d_key )
         
         
          if ( lead_connection.isIncoming()){
           
            header_cipher_out  = rc4_engine_a;
            header_cipher_in  = rc4_engine_b;
 
           out_seq_generator = new SequenceGenerator( new Random( bytesToLong( d_key )), rc4_engine_c, false );
           in_seq_generator  = new SequenceGenerator( new Random( bytesToLong( c_key )), rc4_engine_d, true );
           random = new Random( bytesToLong( d_key, 8 ));
          
          }else{
           
               header_cipher_out  = rc4_engine_b;
            header_cipher_in  = rc4_engine_a;
          
           in_seq_generator  = new SequenceGenerator( new Random( bytesToLong( d_key )), rc4_engine_c, true );
           out_seq_generator = new SequenceGenerator( new Random( bytesToLong( c_key )), rc4_engine_d, false );
          
           random = new Random( bytesToLong( c_key, 8 ));
          }
         
            // as the first packet each way is crypto we skip a sequence number from each generator
            // to represent this and initialise the last-in-order seq appropriately so a sensible value is
            // spliced into the next packet
         
          out_seq_generator.getNextSequenceNumber();
         
          int[]  initial_in_seqs = in_seq_generator.getNextSequenceNumber();
         
          receive_last_inorder_alt_sequence = initial_in_seqs[3];
         
          crypto_done  = true;
         
      }else if ( !crypto_done ){
       
        Debug.out( "Secondary setSecret but crypto not done" );
      }
    }catch( Throwable e ){
     
      Debug.printStackTrace(e);
     
      connection.close( "Crypto problems: "+ Debug.getNestedExceptionMessage(e));
    }
  }
 
  protected RC4Engine
  getCipher(
    byte[]      key )
  {
      SecretKeySpec  secret_key_spec = new SecretKeySpec( key, "RC4" );
     
      RC4Engine rc4_engine  = new RC4Engine();
   
    CipherParameters  params_a = new KeyParameter( secret_key_spec.getEncoded());
   
      // for RC4 enc/dec is irrelevant
   
    rc4_engine.init( true, params_a );
   
      // skip first 1024 bytes of stream to protected against a Fluhrer, Mantin and Shamir attack
     
      byte[]  temp = new byte[1024];
 
      rc4_engine.processBytes( temp, 0, temp.length, temp, 0 );
     
      return( rc4_engine );
  }
 
  protected void
  sendTimerBase()
  {
      // only the outgoing side of a connection can initiate changes in timer base
   
    if ( !outgoing ){
     
      return;
    }
   
      // the thing that kills a connection is too many retransmits so we need to keep these minimised during timer changes
   
      // when we increase our base timer we can immediately increase our retransmit timer but need to wait until they have
      // modified their timer before increasing the ack. Otherwise they won't be receiving acks as fast as they expect and therefore
      // trigger retransmits
   
      // when we decrease our base timer it is the opposite way around - we can immediately decrease acks but delay retrans
   
    synchronized( this ){
     
      if ( timer_is_adjusting ){
       
        return;
      }
        // only consider the stats if we've sent at least a few this interval
     
      if ( stats_packets_unique_sent > 2 ){
       
        int  new_timer_base = current_timer_base;
               
        if ( stats_packets_resent_via_timer > 0 ){
         
          float  resend_ratio = (float)stats_packets_resent_via_timer / stats_packets_unique_sent;
         
          // System.out.println( "resend ratio: " + resend_ratio );
         
          if ( resend_ratio >= 0.25 ){
           
            new_timer_base = (int)( current_timer_base * ( resend_ratio + 1 ));
           
            // round to 100th sec as we send 100ths over the wire and expect to get it back
           
            new_timer_base = (new_timer_base/10)*10;
 
            new_timer_base = Math.min( TIMER_BASE_MAX, new_timer_base );         
           
            if ( new_timer_base != current_timer_base ){
             
              if ( manager.trace()){
               
                trace( "Increasing timer base from " + current_timer_base + " to " + new_timer_base + " due to resends (ratio=" + resend_ratio + ")" );
              }
            }
          }
        }
       
        if ( new_timer_base == current_timer_base && stats_packets_unique_received > 2 ){
         
          float  duplicate_ratio = (float)stats_packets_duplicates / stats_packets_unique_received;

            // we use duplicate packets sometimes to force sequence numbers through, so
            // reduce our sensitivity this them
         
          duplicate_ratio = duplicate_ratio/2;
         
          // System.out.println( "duplicate ratio: " + duplicate_ratio );

          if ( duplicate_ratio >= 0.25 ){
           
            new_timer_base = (int)( current_timer_base * ( duplicate_ratio + 1 ));
           
            new_timer_base = (new_timer_base/10)*10;

            new_timer_base = Math.min( TIMER_BASE_MAX, new_timer_base );
           
            if ( new_timer_base != current_timer_base ){

              if ( manager.trace() ){
               
                trace( "Increasing timer base from " + current_timer_base + " to " + new_timer_base + " due to duplicates (ratio=" + duplicate_ratio + ")" );
              }
            }
          }
        }
       
        if ( new_timer_base == current_timer_base && stats_packets_unique_received > 2 ){
         
            // conservative approach - reduce by 10% if we've had no errors
         
          if ( stats_packets_resent_via_timer == 0 && stats_packets_duplicates == 0 ){
           
            new_timer_base = current_timer_base - (current_timer_base/10);
           
            new_timer_base = (new_timer_base/10)*10;

            new_timer_base = Math.max( new_timer_base, TIMER_BASE_MIN );
           
            if ( new_timer_base != current_timer_base ){

              if ( manager.trace()){
               
                trace( "Decreasing timer base from " + current_timer_base + " to " + new_timer_base  );
              }
            }
          }
        }       
       
        boolean  reset_stats  = false;
       
        long  now = SystemTime.getCurrentTime();

        if ( new_timer_base == current_timer_base ){
         
          if ( now < stats_reset_time || now - stats_reset_time > STATS_RESET_TIMER ){
                       
            reset_stats  = true;
          }
         
        }else{
         
          timer_is_adjusting  = true;
         
          old_timer_base    = current_timer_base;
          current_timer_base  = new_timer_base;
         
          reset_stats = true;
        }
       
        if ( reset_stats ){
         
          resetTimerStats();
        }
      }
    }
  }
 
  protected void
  resetTimerStats()
  {
    stats_reset_time = SystemTime.getCurrentTime();

    stats_packets_unique_sent      = 0;
    stats_packets_resent_via_timer    = 0;
    stats_packets_duplicates      = 0;
    stats_packets_unique_received    = 0;
  }
 
  protected void
  receiveTimerBase(
    int  theirs )
  {   
    synchronized( this ){

      if ( theirs != current_timer_base){
       
        if ( manager.trace() ){
     
          trace( "Received timer base: current=" + current_timer_base + ",theirs=" + theirs + "(adj=" + timer_is_adjusting + ")" );
        }
      }

      if ( outgoing ){

        if ( theirs == current_timer_base ){
         
          if ( timer_is_adjusting ){
           
            timer_is_adjusting = false;
           
            resetTimerStats();
          }
        }
      }else{
     
          // simply use the new value
       
        current_timer_base = theirs;
      }
    }
  }
 
  protected void
  timerTick()
 
    throws IOException
  {
    boolean  retrans_expired   = false;
    boolean  ack_expired      = false;
    boolean  keep_alive_expired  = false;
   
    synchronized( this ){
     
      if ( connections.size() == 0 ){
       
        idle_ticks++;
       
      }else{
       
        idle_ticks = 0;
      }
     
      total_tick_count++;
     
      if ( retransmit_ticks > 0 ){
       
        retransmit_ticks--;
       
        if ( retransmit_ticks == 0 ){
         
          retrans_expired = true;
        }
      }
     
      if ( explicitack_ticks > 0 ){
       
        explicitack_ticks--;
       
        if ( explicitack_ticks == 0 ){
         
          ack_expired = true;
        }
      }
     
      if ( keep_alive_ticks > 0 ){
       
        keep_alive_ticks--;
       
        if ( keep_alive_ticks == 0 ){
         
          keep_alive_expired = true;
        }
      }
     
      stats_log_ticks--;
     
      if ( stats_log_ticks == 0 ){
       
        logStats();
       
        stats_log_ticks = STATS_LOG_TICKS;
      }
    }
   
    if ( retrans_expired ){
     
      retransmitExpired();
    }
     
    if ( ack_expired ){
     
      sendAckCommand( true );
    }
   
    if ( keep_alive_expired ){
     
      sendStatsRequest();
    }
  }
 
  protected int
  getRetransmitTicks()
  {
    int  timer_to_use;
   
    synchronized( this ){
     
      if ( timer_is_adjusting ){
       
        if ( current_timer_base > old_timer_base ){
         
          timer_to_use = current_timer_base;
         
        }else{
         
          timer_to_use = old_timer_base;
        }
      }else{
       
        timer_to_use = current_timer_base;
      }
    }
     
      // 5/3 of base
     

    int  timer = ( timer_to_use * 5 ) / 3;
   
    return( Math.max( 1, timer / UDPConnectionManager.TIMER_TICK_MILLIS ));
  }
 
  protected int
  getExplicitAckTicks()
  {
    int  timer_to_use;
   
    synchronized( this ){
     
      if ( timer_is_adjusting ){
       
        if ( current_timer_base > old_timer_base ){
         
          timer_to_use = old_timer_base;
         
        }else{
         
          timer_to_use = current_timer_base;
        }
      }else{
       
        timer_to_use = current_timer_base;
      }
    }
   
    return( Math.max( 1, timer_to_use / UDPConnectionManager.TIMER_TICK_MILLIS ));
  }
 
  protected void
  startKeepAliveTimer()
  {
    keep_alive_ticks = MIN_KEEPALIVE_TICKS + random.nextInt( MAX_KEEPALIVE_TICKS - MIN_KEEPALIVE_TICKS );
  }
 
  protected void
  stopKeepAliveTimer()
  {
    keep_alive_ticks  = 0;
  }
 
  protected boolean
  idleLimitExceeded()
  {
    if ( idle_ticks > IDLE_TICKS + (int)(Math.random()*2000)){
     
      synchronized( connections ){
       
        if ( connections.size() == 0 ){
       
          failed  = true;
         
          return( true );
        }
      }
    }
   
    return( false );
  }
 
  protected UDPPacket
  getRetransmitPacket()
  {
    Iterator  it = transmit_unack_packets.iterator();
   
    while( it.hasNext()){
     
      UDPPacket  p = (UDPPacket)it.next();
   
      if ( !p.hasBeenReceived()){
     
        boolean  auto_retrans = p.isAutoRetransmit();
       
          // non-auto-retrans only applies to packets if they are the last packet sent
       
        if ( auto_retrans || it.hasNext()){
       
          return( p );
        }
      }
    }
   
    return( null );
  }
 
  protected int
  getRetransmitTicks(
    int  resend_count )
  {
    int ticks = getRetransmitTicks();
   
    int  res;
   
    if ( resend_count == 0 ){
     
      res = ticks;
     
    }else{
     
      res = ticks + (( MAX_RETRANSMIT_TICKS - ticks ) * resend_count ) / ( RETRANSMIT_COUNT_LIMIT-1 );
    }
   
    // System.out.println( "retry: " + res );
   
    return( res );
  }
 
  protected void
  retransmitExpired()
 
    throws IOException
  {
    UDPPacket  packet_to_send = null;
   
    synchronized( this ){
     
      packet_to_send = getRetransmitPacket();
     
      if ( packet_to_send != null ){
       
        stats_packets_resent_via_timer++;
        total_packets_resent_via_timer++;
       
        packet_to_send.resent();
      }
    }
   
    if ( packet_to_send != null ){
     
      if ( manager.trace() ){
        trace( "Retransmit: " + packet_to_send.getString());
      }

      send( packet_to_send );
    }
  }
 
  protected boolean
  remoteLastInSequence(
    int      alt_sequence )
  {
      // if we find this packet then we can also discard any prior to it as they are implicitly
      // ack too
     
    synchronized( this ){

      for (int i=0;i<transmit_unack_packets.size();i++){
       
        UDPPacket  packet = (UDPPacket)transmit_unack_packets.get(i);
       
        if ( packet.getAlternativeSequence() == alt_sequence ){
             
          receive_their_last_inorder_sequence  = packet.getSequence();
         
          for (int j=0;j<=i;j++){
           
            transmit_unack_packets.remove(0);
          }

          return( true );
        }
      }
    }
   
    return( false );
  }
 
  protected synchronized void
  dumpState()
  {
    if ( manager.trace() ){
      String  str = "State:";
     
      String  unack = "";
     
      for (int i=0;i<transmit_unack_packets.size();i++){
       
        UDPPacket  packet = (UDPPacket)transmit_unack_packets.get(i);
       
        unack += (i==0?"":",") + packet.getString();
      }
     
      str += "unack=" + unack + ",last_in_order=" + receive_last_inorder_sequence +
           ",current_in_seq=" + current_receive_unack_in_sequence_count +
           ",sent_in_seq=" + sent_receive_unack_in_sequence_count +
           ",current_oo=" + current_receive_out_of_order_count +
           ",sent_oo=" + sent_receive_out_of_order_count;
     
      /*
      String  done = "";
     
      for (int i=0;i<receive_done_sequences.size();i++){
       
        done += (i==0?"":",") + receive_done_sequences.get(i);
      }
     
      str += ",done=" + done;
      */
     
      String  oo = "";
     
      for (int i=0;i<receive_out_of_order_packets.size();i++){
       
        Object[] entry = (Object[])receive_out_of_order_packets.get(i);
       
        oo += (i==0?"":",") + entry[0] + "/" + entry[1] + "/" + (entry[2]==null?"null":String.valueOf(((ByteBuffer)entry[2]).remaining()));
      }
     
      str += ",oo=" + oo;
     
      str += ",sent_data=" + total_data_sent +"/" + total_data_resent + ",sent_prot=" + total_protocol_sent + "/" + total_protocol_resent;
     
      trace( str );
    }
  }
   
   
 
 
  protected void
  send(
    UDPPacket    packet )
 
    throws IOException
  {
    if ( failed ){
     
      throw( new IOException( "Connection set has failed" ));
    }
   
    byte[]  payload = packet.getBuffer();
     
    if ( manager.trace()){
      trace( packet.getConnection(), "Write: " + packet.getString());
    }
           
    synchronized( this ){
   
      total_packets_sent++;

      int  resend_count = packet.getResendCount();
     
      if ( resend_count > RETRANSMIT_COUNT_LIMIT ){
       
        throw( new IOException( "Packet resend limit exceeded" ));
      }
     
        // all packets carry an implicit ack, pick up the corresponding count here

      long  unackin = packet.getUnAckInSequenceCount();
     
      if ( unackin >   sent_receive_unack_in_sequence_count ){
       
        sent_receive_unack_in_sequence_count  = unackin;
      }
     
        // trigger the retransmit timer if any sent packets have the auto-retransmit property
     
      UDPPacket  retransmit_target = getRetransmitPacket();
     
      if ( retransmit_target == null ){
       
          // no auto-retransmit packet, cancel timer
       
        retransmit_ticks      = 0;
       
      }else if (   retransmit_target != current_retransmit_target ||
            retransmit_target == packet){
       
          // auto-retransmit packet has changed or we are currently retransmitting it, reset timer
       
        retransmit_ticks = getRetransmitTicks( resend_count );

      }else{
       
          // current retry target timer expired, restart it
       
        if ( retransmit_ticks == 0 ){
         
          retransmit_ticks = getRetransmitTicks( resend_count );
        }
      }
     
      current_retransmit_target  = retransmit_target;

        // splice in the latest received in sequence alternative seq if non-crypto packet
     
      if ( packet.getAlternativeSequence() != -1 ){
       
        byte[]  alt = intToBytes( receive_last_inorder_alt_sequence );
       
        payload[0] = alt[0];
        payload[1] = alt[1];
        payload[8] = alt[2];
        payload[9] = alt[3];
      }
   
      int send_count = packet.sent( total_tick_count );

      if ( send_count == 1 ){
       
        if ( packet.getCommand() == UDPPacket.COMMAND_DATA ){
     
          total_data_sent++;
         
        }else{

          total_protocol_sent++;
        }
      }else{
       
        if ( packet.getCommand() == UDPPacket.COMMAND_DATA ){
         
          total_data_resent++;
         
        }else{

          total_protocol_resent++;
        }
      }
    }
   
    manager.send( local_port, remote_address, payload );
  }
 
  public void
  receive(
    byte[]      initial_data,
    int        initial_data_length )
 
    throws IOException
  {
    if ( failed ){
     
      throw( new IOException( "Connection set has failed" ));
    }
   
    dumpState();
   
    if ( manager.trace() ){
      trace( "Read: total=" + initial_data_length );
    }
   
    synchronized( this ){
     
      total_packets_received++;
    }
   
    ByteBuffer  initial_buffer = ByteBuffer.wrap( initial_data );
   
    initial_buffer.limit( initial_data_length );
   
    if ( !crypto_done ){
     
        // first packet - connection setup and crypto handshake
     
        // derive the sequence number in the normal way so that if a retranmission occurs
        // after crypto has been setup then it'll get handled correctly as a dupliate packet
        // below
     
      initial_buffer.position( 4 );
     
      Integer  pseudo_seq = new Integer( initial_buffer.getInt());
     
      initial_buffer.position( 0 );
     
      if ( !receive_done_sequences.contains( pseudo_seq )){
       
        receive_done_sequences.addFirst( pseudo_seq );
     
        if ( receive_done_sequences.size() > RECEIVE_DONE_SEQ_MAX ){
         
          receive_done_sequences.removeLast();
        }
      }
     
      if ( outgoing ){
       
          // a reply received by the initiator acknowledges that the initial message sent has
          // been received
       
        remoteLastInSequence( -1 );
      }
     
      receiveCrypto( initial_buffer );
     
    }else{
     
        // pull out the alternative last-in-order seq
     
      byte[]  alt_seq = new byte[4];
     
      alt_seq[0= initial_data[0];
      alt_seq[1= initial_data[1];
      alt_seq[2= initial_data[8];
      alt_seq[3= initial_data[9];
     
      int  alt = bytesToInt( alt_seq, 0 );
           
      boolean  write_select = remoteLastInSequence( alt );
     
      boolean  lazy_ack_found  = false;

      try{
        initial_buffer.getInt()// seq1
       
        Integer  seq2 = new Integer( initial_buffer.getInt());
       
        initial_buffer.getInt()// seq3
         
          // first see if we know about this sequence number already
       
        if ( receive_done_sequences.contains( seq2 )){
         
          if ( manager.trace() ){
            trace( "Duplicate processed packet: " + seq2 );
          }
         
            // if we're gone quiescent and our lazy-ack packet failed to be delivered we end up here with the other end
            // resending their last message. We pick up this delivery failure and resend the packet
         
          UDPPacket  packet_to_send = null;
         
          synchronized( this ){
           
            stats_packets_duplicates++;
            total_packets_duplicates++;
           
            if ( transmit_unack_packets.size() == 1 ){
             
              UDPPacket  packet = (UDPPacket)transmit_unack_packets.get(0);
             
              if ( !packet.isAutoRetransmit()){
               
                if ( total_tick_count - packet.getSendTickCount() >= MIN_RETRANSMIT_TICKS ){
               
                  if ( manager.trace() ){
                    trace( "Retrans non-auto-retrans packet" );
                  }
                 
                  packet_to_send = packet;
                }
              }
            }
          }
         
          if ( packet_to_send != null ){
           
            send( packet_to_send );
          }
         
          return;
        }
       
        if ( !out_seq_generator.isValidAlterativeSequence( alt )){
           
          if ( manager.trace() ){
            trace( "Received invalid alternative sequence " + alt + " - dropping packet" );
          }
         
          return;
        }

        boolean  oop = false;
       
        for (int i=0;i<receive_out_of_order_packets.size();i++){
         
          Object[]  entry = (Object[])receive_out_of_order_packets.get(i);
         
          Integer  oop_seq = (Integer)entry[0];
         
          ByteBuffer  oop_buffer = (ByteBuffer)entry[2];
         
          if ( oop_seq.equals( seq2 )){
           
            synchronized( this ){

              if ( oop_buffer != null ){
           
                stats_packets_duplicates++;
                total_packets_duplicates++;
               
                if ( manager.trace() ){
                  trace( "Duplicate out-of-order packet: " + seq2 );
                }
               
                return;
              }
             
              stats_packets_unique_received++;
              total_packets_unique_received++;
             
              if ( manager.trace() ){
                trace( "Out-of-order packet entry data matched for seq " + seq2 );
              }
             
                // got data matching out-of-order-entry, add it in!
                         
              entry[2] = initial_buffer;
             
              oop = true;
             
              break;
            }
          }
        }
       
        if ( !oop ){
         
            // not a known out-of-order packet. If our oop list is full then all we can do is drop
            // the packet
         
          boolean  added = false;
         
          while ( receive_out_of_order_packets.size() < RECEIVE_OUT_OF_ORDER_PACKETS_MAX ){
                 
            int[]  seq_in = in_seq_generator.getNextSequenceNumber();
               
            if ( seq2.intValue() == seq_in[1] ){
               
              synchronized( this ){
               
                stats_packets_unique_received++;
                total_packets_unique_received++;
              }
             
              if ( receive_out_of_order_packets.size() == 0 ){

                // this is an in-order packet :)

              }else{
               
                if ( manager.trace() ){
                  trace( "Out-of-order packet entry adding for seq " + seq_in[1] );
                }
              }
                           
              receive_out_of_order_packets.add( new Object[]{ seq2, new Integer( seq_in[3]), initial_buffer } );
             
              added = true;
             
              break;
             
            }else{
             
              if ( manager.trace() ){
                trace( "Out-of-order packet: adding spacer for seq " + seq_in[1] );
              }
             
              receive_out_of_order_packets.add( new Object[]{ new Integer( seq_in[1]), new Integer( seq_in[3]), null } );
            }
          }
         
          if ( !added ){
         
              // drop the packet, we have no room to store it
           
            if ( manager.trace() ){
              trace( "Out-of-order packet dropped as too many pending" );
            }
           
            return;
          }
        }
       
        boolean  this_is_oop   = true;
       
          // process any ready packets
       
        Iterator  it = receive_out_of_order_packets.iterator();
       
        while( it.hasNext()){
         
          Object[]  entry = (Object[])it.next();
         
          ByteBuffer  buffer = (ByteBuffer)entry[2];
         
          if ( buffer == null ){
           
            break;
          }
       
          it.remove();
       
          byte[]  data = buffer.array();
         
          if ( buffer == initial_buffer ){
           
            this_is_oop = false;
          }
         
          synchronized( this ){
           
            current_receive_unack_in_sequence_count++;
          }
         
          Integer  seq = (Integer)entry[0];
         
          receive_last_inorder_sequence    = seq.intValue();
          receive_last_inorder_alt_sequence  = ((Integer)entry[1]).intValue();
         
          if ( !receive_done_sequences.contains( seq )){
           
            receive_done_sequences.addFirst( seq );
         
            if ( receive_done_sequences.size() > RECEIVE_DONE_SEQ_MAX ){
             
              receive_done_sequences.removeLast();
            }
          }
         
          header_cipher_in.processBytes( data, 12, 2, data, 12 );
   
          int  header_len = buffer.getShort()&0xffff;
         
          if ( header_len > data.length ){
           
            if ( manager.trace() ){
              trace( "Header length too large" );
            }
           
            return;
          }
         
          header_cipher_in.processBytes( data, 14, header_len-14, data, 14 );
         
          SHA1Hasher  hasher = new SHA1Hasher();
         
          hasher.update( data, 4, 4 );
          hasher.update( data, 12, header_len - 4 - 12 );
                 
          byte[]  hash = hasher.getDigest();
             
          for (int i=0;i<4;i++){
           
            if ( hash[i] != data[header_len-4+i] ){ 
           
              if ( manager.trace() ){
                trace( "hash incorrect" );
              }
             
              return;
            }
          }
         
          byte  version = buffer.get();
         
          if ( version != UDPPacket.PROTOCOL_VERSION ){
           
            // continue, assumption is that version changes are backward compatible
           
            // throw( new IOException( "Invalid protocol version '" + version + "'" ));
          }
         
          byte  flags = buffer.get();
               
          if ( ( flags & UDPPacket.FLAG_LAZY_ACK ) != 0 ){
           
            lazy_ack_found  = true;
          }
         
          int  their_timer_base = (buffer.getShort()&0xffff)*10;
         
          receiveTimerBase( their_timer_base );
         
          byte  command = buffer.get();
         
          if ( command == UDPPacket.COMMAND_DATA ){       
 
            receiveDataCommand( seq.intValue(), buffer, header_len );
             
          }else if ( command == UDPPacket.COMMAND_ACK ){
           
            receiveAckCommand( buffer );
           
          }else if ( command == UDPPacket.COMMAND_CLOSE ){
           
            receiveCloseCommand( buffer );
 
          }else if ( command == UDPPacket.COMMAND_STAT_REQUEST ){

            receiveStatsRequest( buffer );
           
          }else if ( command == UDPPacket.COMMAND_STAT_REPLY ){

            receiveStatsReply( buffer );
           
          }else{
         
            // ignore unrecognised commands to support future change
          }
        }
       
        if ( this_is_oop ){
         
          synchronized( this ){
         
            current_receive_out_of_order_count++;
            total_packets_out_of_order++;
          }
        }
      }finally{
     
        boolean  send_ack = false;
       
        synchronized( this ){
         
          long  unack_diff   = current_receive_unack_in_sequence_count  - sent_receive_unack_in_sequence_count;
          long  oos_diff  = current_receive_out_of_order_count - sent_receive_out_of_order_count;
         
          if (   unack_diff > RECEIVE_UNACK_IN_SEQUENCE_LIMIT ||
              oos_diff  > RECEIVE_OUT_OF_ORDER_ACK_LIMIT ){
           
            send_ack = true;
          }
        }
       
        if ( send_ack ){
         
          sendAckCommand( false );
        }
         
        synchronized( this ){

            // if we have either received in-order packets that we haven't sent an ack for or
            // out-of-order packets start the ack timer
         
            // only exception is if we have only a lazy-ack packet outstanding and no out-of-order
         
          long  unack_diff   = current_receive_unack_in_sequence_count  - sent_receive_unack_in_sequence_count;
         
          if ( unack_diff == 1 && lazy_ack_found && receive_out_of_order_packets.size() == 0 ){
           
            if ( manager.trace() ){
              trace( "Not starting ack timer, only lazy ack received" );
            }
           
            startKeepAliveTimer();
           
          }else{
           
            stopKeepAliveTimer();
           
            if ( unack_diff > 0 || receive_out_of_order_packets.size() > 0 ){
             
              if ( explicitack_ticks == 0 ){
               
                explicitack_ticks = getExplicitAckTicks();
              }
            }
          }
        }
     
        if ( write_select ){
         
          synchronized( connection_writers ){
 
            Iterator  it = connection_writers.iterator();
           
            while( it.hasNext()){
             
              UDPConnection  c = (UDPConnection)it.next();
             
              if ( c.isConnected()){
               
                  // we can safely do this while holding monitor as this simply queues an async selector notification if required
               
                c.sent();
               
              }else{
               
                it.remove();
              }
            }
          }
        }
      }
    }
  }
 
 
  protected int
  sendCrypto(
    ByteBuffer[]    buffers,
    int          offset,
    int          length )
 
    throws IOException
  {
    // regardless of mss we have to get the first phe handshake messages into a single packet

    int  payload_to_send  = 0;

    for (int i=offset;i<offset+length;i++){
     
      payload_to_send += buffers[i].remaining();
    }
   
    // first packet, cram it all in
   
    byte[]  packet_bytes = new byte[ payload_to_send ];
   
    ByteBuffer packet_buffer = ByteBuffer.wrap( packet_bytes );
   
    for (int i=offset;i<offset+length;i++){
     
      packet_buffer.put( buffers[i] );
    }
     
    UDPPacket packet_to_send = new UDPPacket( lead_connection, new int[]{ -1, -1, -1, -1 }, UDPPacket.COMMAND_CRYPTO, packet_bytes, 0 )
   
    synchronized( this ){
     
      stats_packets_unique_sent++;
      total_packets_unique_sent++;
     
      transmit_unack_packets.add( packet_to_send );
    }
   
    if ( manager.trace() ){
      trace( "sendCrypto: seq=" + packet_to_send.getSequence() + ", len=" + payload_to_send );
    }
   
    send( packet_to_send );
 
    return( payload_to_send );
  }
 
  protected void
  receiveCrypto(
    ByteBuffer    buffer )
 
    throws IOException
  {
    boolean  new_connection = false;
   
    UDPConnection  connection = null;
   
    synchronized( connections ){

      if ( failed ){
       
        throw( new IOException( "Connection set has failed" ));
      }
     
      if ( connections.size() == 0 ){
       
          // -1 for connection id as we don't know what it is yet
       
        connection  = new UDPConnection( this, -1 );
   
        connections.put( new Integer( connection.getID()), connection );
         
        lead_connection  = connection;
       
        new_connection  = true;
       
      }else{
       
        connection = lead_connection;
      }
    }
         
    if ( new_connection ){
     
      manager.accept( local_port, remote_address, connection );
    }
     
    if ( manager.trace() ){
      trace( connection, "readCrypto: rem="+ buffer.remaining());
    }
   
    connection.receive( buffer );
  }

 
  protected int
  sendDataCommand(
    UDPConnection    connection,
    ByteBuffer[]    buffers,
    int          offset,
    int          length )
 
    throws IOException
  {
    int  payload_to_send  = 0;

    for (int i=offset;i<offset+length;i++){
     
      payload_to_send += buffers[i].remaining();
    }
   
    byte[]  header = new byte[256];
   
    ByteBuffer  header_buffer = ByteBuffer.wrap( header );
   
    UDPPacket  packet_to_send;
   
    synchronized( this ){
     
      long  unack_in_sequence_count  = current_receive_unack_in_sequence_count;
     
      int[]  sequence_numbers = writeHeaderStart( header_buffer, UDPPacket.COMMAND_DATA, UDPPacket.FLAG_NONE );
     
      header_buffer.putInt( connection.getID());
     
      int header_size = writeHeaderEnd( header_buffer, false );
                 
      int  mss = connection.getTransport().getMss();
 
        // just in case we have some crazy limit set
     
      if ( mss < MIN_MSS ){
       
        mss = MIN_MSS;
      }
     
      if ( payload_to_send > mss - header_size ){
       
        payload_to_send  = mss - header_size;
      }
     
      if ( payload_to_send < 0 ){
       
        payload_to_send  = 0;
      }
         
      byte[]  packet_bytes = new byte[ header_size + payload_to_send ];
     
      ByteBuffer packet_buffer = ByteBuffer.wrap( packet_bytes );
     
      packet_buffer.put( header, 0, header_size );
     
      int  rem = payload_to_send;
     
      for (int i=offset;i<offset+length;i++){
       
        ByteBuffer  buffer = buffers[i];
       
        int  limit = buffer.limit();
       
        try{
          if ( buffer.remaining() > rem ){
           
            buffer.limit( buffer.position() + rem );
          }
         
          rem -= buffer.remaining();
         
          packet_buffer.put( buffer );
         
        }finally{
         
          buffer.limit( limit );
        }
       
        if ( rem == 0 ){
         
          break;
        }
      }
       
      packet_to_send = new UDPPacket( connection, sequence_numbers, UDPPacket.COMMAND_DATA, packet_bytes, unack_in_sequence_count );
     
      transmit_unack_packets.add( packet_to_send );
    }

    if ( manager.trace() ){
      trace( connection, "sendData: seq=" + packet_to_send.getSequence() + ",data="+ payload_to_send );
    }
   
    send( packet_to_send );
 
    return( payload_to_send );
  }
 
  protected void
  receiveDataCommand(
    int        sequence,
    ByteBuffer    buffer,
    int        header_length )
 
    throws IOException
  {
    int  connection_id = buffer.getInt();
   
    UDPConnection  connection     = null;
    boolean      new_connection   = false;
   
    synchronized( connections ){
     
      if ( failed ){
       
        throw( new IOException( "Connection set has failed" ));
      }
     
      connection = (UDPConnection)connections.get( new Integer( connection_id ));
     
      if ( connection == null ){
               
        connection = (UDPConnection)connections.remove( new Integer( -1 ));

        if ( connection != null ){
           
          connection.setID( connection_id );
           
          connections.put( new Integer( connection_id ), connection );
        }
      }
     
      if ( connection == null ){
       
        if ( connections.size() == 128 ){
         
          throw( new IOException( "Connection limit reached" ));
        }
       
        connection  = new UDPConnection( this, connection_id );
       
        connections.put( new Integer( connection.getID()), connection );
                       
        new_connection  = true;
      }
    }
   
    buffer.position( header_length );
   
    if ( new_connection ){
     
      manager.accept( local_port, remote_address, connection );
    }
   
    if ( manager.trace() ){
      trace( connection, "receiveData: seq=" + sequence + ",data="+ buffer.remaining());
    }
   
    connection.receive( buffer );
  }
 
 
 
  protected void
  sendAckCommand(
    boolean  timer_expired )
 
    throws IOException
  {
    UDPPacket  packet_to_send = null;

    synchronized( this ){
                 
        // if there's already an ACK packet outstanding then we just resend that one
     
      Iterator  it = transmit_unack_packets.iterator();
     
      while( it.hasNext()){
       
        UDPPacket  packet = (UDPPacket)it.next();
       
        if ( packet.getCommand() == UDPPacket.COMMAND_ACK ){
         
          if ( total_tick_count - packet.getSendTickCount() >= getExplicitAckTicks() ){
         
            if ( manager.trace() ){
              trace( packet.getConnection(), "retransAck:" + packet.getString());
            }
           
            packet_to_send  = packet;
           
            break;
           
          }else{
         
              // sent too recently, bail out
           
            return;
          }
        }
      }
     
      if ( packet_to_send == null ){
     
        byte[]  header_bytes = new byte[256 + (RECEIVE_OUT_OF_ORDER_PACKETS_MAX+1)*4];
       
        ByteBuffer  header = ByteBuffer.wrap( header_bytes );
       
        long  unack_in_sequence_count  = current_receive_unack_in_sequence_count;
 
          // if this is the only packet, contains nothing out-of-sequence and timer invoked then the connection has
          // gone idle - don't auto-retransmit to allow things to quiesce
     
        boolean  no_retrans = transmit_unack_packets.size() == 0 && timer_expired && receive_out_of_order_packets.size() == 0;
       
        int[]  sequences = writeHeaderStart( header, UDPPacket.COMMAND_ACK, no_retrans?UDPPacket.FLAG_LAZY_ACK:UDPPacket.FLAG_NONE );
       
        it = receive_out_of_order_packets.iterator();
       
        String  oos_str = "";
       
        int  count = 0;
       
        while( it.hasNext() && count < MAX_CONTIGUOUS_RETRANS_FOR_ACK ){
         
          Object[]  entry = (Object[])it.next();
       
          if ( entry[2] != null ){
         
            int  out_of_order_seq = ((Integer)entry[0]).intValue();
            int  out_of_rep_seq = ((Integer)entry[1]).intValue();
                 
            oos_str += (oos_str.length()==0?"":",") + out_of_order_seq + "/" + out_of_rep_seq;
           
            header.putInt(out_of_order_seq);
           
            count++;
          }
        }
       
        header.putInt( -1 );
       
        if ( count == 0 ){
         
          sent_receive_out_of_order_count    = current_receive_out_of_order_count;
         
        }else{
       
          sent_receive_out_of_order_count += count;
         
          if ( sent_receive_out_of_order_count > current_receive_out_of_order_count ){
           
            sent_receive_out_of_order_count    = current_receive_out_of_order_count;
          }
        }
       
        int  size = writeHeaderEnd( header, true );
       
        byte[]  packet_bytes = new byte[size];
       
        System.arraycopy( header_bytes, 0, packet_bytes, 0, size );
               
        packet_to_send = new UDPPacket( lead_connection, sequences, UDPPacket.COMMAND_ACK, packet_bytes, unack_in_sequence_count );
           
        if ( no_retrans){
         
          packet_to_send.setAutoRetransmit( false );
         
          startKeepAliveTimer();
        }
       
        transmit_unack_packets.add( packet_to_send );

        if ( manager.trace() ){
          trace( lead_connection, "sendAck: in_seq=" + receive_last_inorder_sequence + ",out_of_seq=" + oos_str );
        }
      }
    }
   
    /*
    if ( timer_expired ){
      System.out.println( getName() + ": ack timer");
    }
    */
   
    send( packet_to_send );
  }

  protected void
  receiveAckCommand(
    ByteBuffer    buffer )
 
    throws IOException
  {
    List  resend_list = new ArrayList();

    String  oos_str = "";

    synchronized( this ){ 
       
      Iterator it = transmit_unack_packets.iterator();

      while( resend_list.size() < MAX_CONTIGUOUS_RETRANS_FOR_ACK ){
       
        int  out_of_order_seq = buffer.getInt();
       
        if ( out_of_order_seq == -1 ){
         
          break;
         
        }else{
           
          if ( manager.trace() ){
            oos_str += (oos_str.length()==0?"":",") + out_of_order_seq;
          }

          while( it.hasNext() && resend_list.size() < MAX_CONTIGUOUS_RETRANS_FOR_ACK ){
           
            UDPPacket  packet = (UDPPacket)it.next();
           
            if ( packet.getSequence() == out_of_order_seq ){
             
                // can't remove the packet here as its presence is required to allow an in-order
                // ack to correctly remove prior packets
             
              packet.setHasBeenReceived();
             
              break;
            }
               
            if ( total_tick_count - packet.getSendTickCount() >= MIN_RETRANSMIT_TICKS ){
             
              if ( !resend_list.contains( packet )){
                 
                resend_list.add( packet );
              }
            }
          }                   
        }
      }
     
      total_packets_resent_via_ack  += resend_list.size();
    }
   
    if ( manager.trace() ){
      trace( "receiveAck: in_seq=" + receive_their_last_inorder_sequence + ",out_of_seq=" + oos_str );
    }
   
    for (int i=0;i<resend_list.size();i++){
       
      send((UDPPacket)resend_list.get(i));
    }     
  }
 
 
  protected void
  sendStatsRequest()
 
    throws IOException
  {
    UDPPacket  packet_to_send = null;

    synchronized( this ){
                 
        // if there's already an stats request packet outstanding then we just resend that one
     
      Iterator  it = transmit_unack_packets.iterator();
     
      while( it.hasNext()){
       
        UDPPacket  packet = (UDPPacket)it.next();
       
        if ( packet.getCommand() == UDPPacket.COMMAND_STAT_REQUEST ){
         
          if ( total_tick_count - packet.getSendTickCount() >= MIN_RETRANSMIT_TICKS ){
         
            if ( manager.trace() ){
              trace( packet.getConnection(), "retransStatsRequest:" + packet.getString());
            }
           
            packet_to_send  = packet;
           
            break;
           
          }else{
         
              // sent too recently, bail out
           
            return;
          }
        }
      }
     
      if ( packet_to_send == null ){
             
        byte[]  header_bytes = new byte[256];
       
        ByteBuffer  header = ByteBuffer.wrap( header_bytes );
       
        long  unack_in_sequence_count  = current_receive_unack_in_sequence_count;
 
        int[]  sequences = writeHeaderStart( header, UDPPacket.COMMAND_STAT_REQUEST, UDPPacket.FLAG_NONE );
       
        int  size = writeHeaderEnd( header, true );
       
        byte[]  packet_bytes = new byte[size];
       
        System.arraycopy( header_bytes, 0, packet_bytes, 0, size );
               
        packet_to_send = new UDPPacket( lead_connection, sequences, UDPPacket.COMMAND_STAT_REQUEST, packet_bytes, unack_in_sequence_count );
       
        transmit_unack_packets.add( packet_to_send );
       
        if ( manager.trace() ){
          trace( lead_connection, "sendStatsRequest" );
        }
      }
    }
           
    send( packet_to_send );
  }

  protected void
  receiveStatsRequest(
    ByteBuffer    buffer )
 
    throws IOException
  {
    UDPPacket  packet_to_send = null;
     
    if ( manager.trace() ){
      trace( "ReceiveStatsRequest" );
    }
   
    synchronized( this ){
                 
        // if there's already an stats reply packet outstanding then we just resend that one
     
      Iterator  it = transmit_unack_packets.iterator();
     
      while( it.hasNext()){
       
        UDPPacket  packet = (UDPPacket)it.next();
       
        if ( packet.getCommand() == UDPPacket.COMMAND_STAT_REPLY ){
         
          if ( total_tick_count - packet.getSendTickCount() >= MIN_RETRANSMIT_TICKS ){
         
            if ( manager.trace() ){
              trace( packet.getConnection(), "retransStatsReply:" + packet.getString());
            }
           
            packet_to_send  = packet;
           
            break;
           
          }else{
         
              // sent too recently, bail out
           
            return;
          }
        }
      }
     
      if ( packet_to_send == null ){
             
        byte[]  header_bytes = new byte[256];
       
        ByteBuffer  header = ByteBuffer.wrap( header_bytes );
       
        long  unack_in_sequence_count  = current_receive_unack_in_sequence_count;
 
        boolean  no_retrans = transmit_unack_packets.size() == 0 && receive_out_of_order_packets.size() == 0;
       
        int[]  sequences = writeHeaderStart( header, UDPPacket.COMMAND_STAT_REPLY, no_retrans?UDPPacket.FLAG_LAZY_ACK:UDPPacket.FLAG_NONE );
               
        int  size = writeHeaderEnd( header, true );
       
        byte[]  packet_bytes = new byte[size];
       
        System.arraycopy( header_bytes, 0, packet_bytes, 0, size );
               
        packet_to_send = new UDPPacket( lead_connection, sequences, UDPPacket.COMMAND_STAT_REPLY, packet_bytes, unack_in_sequence_count );
       
        if ( no_retrans ){
         
          packet_to_send.setAutoRetransmit( false );
        }
       
        transmit_unack_packets.add( packet_to_send );

        if ( manager.trace() ){
          trace( lead_connection, "sendStatsReply" );
        }
      }
    }
           
    send( packet_to_send );   
  }
 
  protected void
  receiveStatsReply(
    ByteBuffer    buffer )
 
    throws IOException
  {
    if ( manager.trace() ){
      trace( "receiveStatsReply" );
    }
  }
 
 
  protected void
  sendCloseCommand(
    UDPConnection    connection )
 
    throws IOException
  {
    if ( crypto_done ){
     
      UDPPacket  packet_to_send;
     
      synchronized( this ){
       
        byte[]  header_bytes = new byte[256];
       
        ByteBuffer  header = ByteBuffer.wrap( header_bytes );
       
        long  unack_in_sequence_count  = current_receive_unack_in_sequence_count;
   
        int[]  sequences = writeHeaderStart( header, UDPPacket.COMMAND_CLOSE, UDPPacket.FLAG_NONE );
       
        header.putInt( connection.getID());
       
        int  size = writeHeaderEnd( header, true );
       
        byte[]  packet_bytes = new byte[size];
       
        System.arraycopy( header_bytes, 0, packet_bytes, 0, size );
       
        if ( manager.trace() ){
          trace( connection, "sendClose" );
        }
       
        packet_to_send = new UDPPacket( lead_connection, sequences, UDPPacket.COMMAND_CLOSE, packet_bytes, unack_in_sequence_count );
       
        transmit_unack_packets.add( packet_to_send );
      }
     
      send( packet_to_send );
       
    }else{
     
      IOException failure = new IOException( "Connection failed during setup phase" );
     
      failed( failure );
     
      throw( failure );
    }
  }
 
  protected void
  receiveCloseCommand(
    ByteBuffer    buffer )
 
    throws IOException
  {
    int  connection_id = buffer.getInt();
   
    UDPConnection  connection     = null;
   
    synchronized( connections ){
     
      if ( failed ){
       
        throw( new IOException( "Connection set has failed" ));
      }
     
      connection = (UDPConnection)connections.get( new Integer( connection_id ));
    }
 
    if ( manager.trace() ){
      trace( "receiveClose: con=" + (connection==null?"<null>":(""+connection.getID())));
    }
   
    if ( connection != null ){
     
      connection.close( "Remote has closed the connection" );
    }
  }
 
 
  protected int[]
  writeHeaderStart(
    ByteBuffer  buffer,
    byte    command,
    byte    flags )
 
    throws IOException
  {   
    // packet format
 
    // int[3] sequence numbers
    // short header length starting from version
    // byte version
    // byte flags
    // short timer base in 100ths second
    // byte command: data, close, re-request
    //
    // command specific data
    //       data: int connection_id
    //    close: int connection_id....
    //     ...
    // for non-data packets: random 0->7 bytes
    // int checksum
    // for data packets: payload
 
    // ***** Change this for DATA and you must change PROTOCOL_DATA_HEADER_SIZE ****
   
    sendTimerBase();
   
    stats_packets_unique_sent++;
    total_packets_unique_sent++;
   
    int[]  sequence_numbers = out_seq_generator.getNextSequenceNumber();
   
    int  seq = sequence_numbers[1];
   
    buffer.putInt( sequence_numbers[0] );
    buffer.putInt( seq );
    buffer.putInt( sequence_numbers[2] );
   
      // insert space for length added later
       
    buffer.putShort((short)0 );
       
    buffer.put((byte)UDPPacket.PROTOCOL_VERSION);
    buffer.put( flags );
    buffer.putShort((short)(current_timer_base/10));
    buffer.put((byte)command);

    return( sequence_numbers );
  }

  protected int
  writeHeaderEnd(
    ByteBuffer  buffer,
    boolean    randomise_size )
 
    throws IOException
  {
    if ( randomise_size ){
     
      int  pad = random.nextInt( 8 );
     
      for (int i=0;i<pad;i++){
       
        buffer.put((byte)0);
      }
    }
   
    short  total_length = (short)buffer.position();
   
    buffer.position( 12 );
   
    buffer.putShort((short)( total_length + 4 ));
   
      // hash includes real sequence + header content but obviously not including the hash
   
    byte[] buffer_bytes = buffer.array();
   
    SHA1Hasher  hasher = new SHA1Hasher();
   
    hasher.update( buffer_bytes, 4, 4 );
    hasher.update( buffer_bytes, 12, total_length - 12 );
     
    byte[] hash = hasher.getDigest();

    buffer.position( total_length );
   
    buffer.put( hash, 0, 4 );
   
    total_length += 4;
   
      // don't encrypt the sequence numbers
   
    header_cipher_out.processBytes( buffer_bytes, 12, total_length-12, buffer_bytes, 12 );
   
    if ( total_length > MAX_HEADER ){
     
      Debug.out( "MAX_HEADER exceeded!!!!" );
     
      throw( new IOException( "MAX_HEADER exceeded" ));
    }
   
    return( total_length );
  }
   
  protected int
  write(
    UDPConnection  connection,
    ByteBuffer[]  buffers,
    int        offset,
    int        length )
 
    throws IOException
  {
    if ( !canWrite( connection )){
     
      return( 0 );
    }
   
    synchronized( connection_writers ){
     
      int  size = connection_writers.size();
     
      if ( size == 0 ){
       
        connection_writers.add( connection );
       
      }else if ( connection_writers.size() == 1 && connection_writers.get(0) == connection ){
       
      }else{
       
        connection_writers.remove( connection );
     
        connection_writers.addLast( connection );
      }
    }
   
    if ( total_packets_sent == 0 ){
     
      return( sendCrypto( buffers, offset, length ));
     
    }else{
   
      return( sendDataCommand( connection, buffers, offset, length ));
    }
  }
 
  protected boolean
  canWrite(
    UDPConnection  connection )
  {
    if ( !crypto_done ){
     
      if ( connection != lead_connection ){
       
        return( false );
      }
     
      if ( total_packets_sent > 0 ){
       
        return( false );
      }
    }
   
    boolean  space =  transmit_unack_packets.size() < MAX_TRANSMIT_UNACK_DATA_PACKETS;
   
    /*
    boolean  old_log = LOG;
   
    LOG = !space;
   
    if ( manager.trace() != old_log ){
     
      System.out.println( "Log: " + (LOG?"On":"Off"));
    }
    */
   
    return( space );
  }
 
  public void
  close(
    UDPConnection  connection,
    String      reason )
  {
    if ( manager.trace() ){
      trace( connection, "close: " + reason );
    }
   
    boolean  found;
   
    synchronized( connections ){
     
      found = connections.containsValue( connection );
    }
   
    if ( found ){
     
      try{
        sendCloseCommand( connection );
       
      }catch( Throwable e ){
       
        failed( e );
      }
    }

      // final poll incase there are ignorant listeners
   
    connection.poll();

    manager.remove( this, connection );
  }
 
  public void
  failed(
    UDPConnection  connection,
    Throwable    reason )
  {
    if ( manager.trace() ){
      trace( connection, "Failed: " + Debug.getNestedExceptionMessage(reason));
    }
   
      // run a final poll operation to inform any selector listeners of the failure
     
    connection.poll();
   
    manager.remove( this, connection );
  }
 
  protected void
  failed(
    Throwable e )
  {
    List  conns = null;
   
    synchronized( connections ){
     
      if ( !failed ){
       
        if ( manager.trace() ){
          trace( "Connection set failed: " + Debug.getNestedExceptionMessage( e ));
        }
     
        failed  = true;

        conns = new ArrayList( connections.values());
      }
    }
   
    if ( conns != null ){
       
      for (int i=0;i<conns.size();i++){
       
        try{
          ((UDPConnection)conns.get(i)).failed( e );
         
        }catch( Throwable f ){
         
          Debug.printStackTrace(f);
        }
      }
     
      manager.failed( this );
    }
  }
 
  protected boolean
  hasFailed()
  {     
    return( failed );
  }
 
  protected void
  removed()
  {
    logStats();
  }
 
  static void
  forDocumentation()
  {
      // this is here to draw attention to the fact that there's a dependency between packet formats...
   
    PRUDPPacketReply.registerDecoders( new HashMap());
  }
 

 
  protected int
  cipherInt(
    RC4Engine  cipher,
    int      i )
  {
    byte[]  bytes = intToBytes( i );
   
    cipher.processBytes( bytes, 0, bytes.length, bytes, 0 );
   
    return( bytesToInt( bytes, 0 ));
  }
 
  protected int
  bytesToInt(
    byte[]  bytes,
    int    offset )
  {
    int   res =   (bytes[offset++]<<24)&0xff000000 |
            (bytes[offset++]<<16)&0x00ff0000 |
            (bytes[offset++]<<8)&0x0000ff00 |
            bytes[offset++]&0x000000ff;
       
    return( res );
  }
 
  protected byte[]
  intToBytes(
    int    i )
  {
    byte[] res = new byte[]{ (byte)(i>>24), (byte)(i>>16), (byte)(i>>8), (byte)i };
   
    return( res );
  }
 
  protected long
  bytesToLong(
    byte[]  bytes )
  {
    return( bytesToLong( bytes, 0 ));
  }
 
  protected long
  bytesToLong(
    byte[]  bytes,
    int    offset )
  {
    long   i1 =   (bytes[offset++]<<24)&0xff000000L |
            (bytes[offset++]<<16)&0x00ff0000L |
            (bytes[offset++]<<8)&0x0000ff00L |
            bytes[offset++]&0x000000ffL;
           
    long   i2 =   (bytes[offset++]<<24)&0xff000000L |
            (bytes[offset++]<<16)&0x00ff0000L |
            (bytes[offset++]<<8)&0x0000ff00L |
            bytes[offset++]&0x000000ffL;       

    long  res = ( i1 << 32 ) | i2;
       
    return( res );
  }
 
  protected String
  getName()
  {
    return( "loc="+local_port + " - " + remote_address );
  }
 
  protected void
  logStats()
  {
    if ( Logger.isEnabled()){
       
      synchronized( this ){
        String  str = "sent: tot=" + total_packets_sent + ",uni=" + total_packets_unique_sent +
                ",ds=" + total_data_sent + ",dr=" + total_data_resent +
                ",ps=" + total_protocol_sent + ",pr=" + total_protocol_resent +
                ",rt=" + total_packets_resent_via_timer + ",ra=" + total_packets_resent_via_ack;
       
        str += " recv: tot=" + total_packets_received + ",uni=" + total_packets_unique_received +
                ",du=" + total_packets_duplicates + ",oo=" + total_packets_out_of_order;
       
        str += " timer=" + current_timer_base + ",adj=" + timer_is_adjusting;
       
        Logger.log(new LogEvent(LOGID, "UDP " + getName() + " - " + str ));
      }
    }
  }
 
  protected void
  trace(
    String    str )
  {
    if ( manager.trace()){
     
      manager.trace( "UDP " + getName() + ": " + str );
    }
  }
 
  protected void
  trace(
    UDPConnection  connection,
    String      str )
  {
    if ( manager.trace()){
     
      manager.trace"UDP " + getName() + " (" + connection.getID() + "): " + str );
    }
  }
 
  protected class
  SequenceGenerator
  {
    private Random    generator;
    private RC4Engine  cipher;
    private boolean    in;
   
    private final int[]  seq_memory;
    private final int[]  alt_seq_memory;
    private int seq_memory_pos;
   
    private int debug_seq_in_next  = outgoing?0:1000000;
    private int debug_seq_out_next  = outgoing?1000000:0;

    protected
    SequenceGenerator(
      Random    _generator,
      RC4Engine  _cipher,
      boolean    _in )
    {
      generator  = _generator;
      cipher    = _cipher;
      in      = _in;
     
      seq_memory    = new int[MAX_SEQ_MEMORY];
      alt_seq_memory  = new int[MAX_SEQ_MEMORY];
     
      Arrays.fill( seq_memory, -1 );
      Arrays.fill( alt_seq_memory, -1 );
    }
   
    protected synchronized int[]
        getNextSequenceNumber()
        {
            // damn tracker udp protocol has:
            // request: long (random connection id) int (action)
            // reply: int (action) int (random txn id)
         
            // now action is always < 2048 so all other uses of udp packets will have either
            // 0x000007ff in either bytes 9 or 0 onwards. So we're forced to use 12 byte sequence numbers
         
            // internally we use the middle integer as the packet sequence
         
            // a secondary identifier for the sequence is also generated to be used in header position
            // 0-2 and 8-10 when reporting last sequences in the clear
             
          final int  mask = 0xfffff800;
         
          while( true ){
           
            int  seq1;
            int  seq2;
            int  seq3;
            int  seq4;
           
            if ( DEBUG_SEQUENCES ){
             
              if ( in ){
                seq1 = 0xffffffff;
                seq2 = debug_seq_in_next;
                seq3 = 0xffffffff;
                seq4 = debug_seq_in_next;
               
                debug_seq_in_next++;
              }else{
                seq1 = 0xffffffff;
                seq2 = debug_seq_out_next;
                seq3 = 0xffffffff;
                seq4 = debug_seq_out_next;
               
                debug_seq_out_next++;
              }
             
             
            }else{
              seq1 = generator.nextInt();
              seq2 = generator.nextInt();
              seq3 = generator.nextInt();
              seq4 = generator.nextInt();
           
              seq1 = cipherInt( cipher, seq1 );
              seq2 = cipherInt( cipher, seq2 );
              seq3 = cipherInt( cipher, seq3 );
              seq4 = cipherInt( cipher, seq4 );
            }
           
          if (( seq1 & mask ) != 0 && seq2 != -1 && ( seq3 & mask ) != 0){
           
            if ( (seq4 & 0xffff0000) != 0 && (seq4 & 0x0000ffff) != 0 ){
             
              boolean  bad  = false;
             
              for (int i=0;i<MAX_SEQ_MEMORY;i++){
               
                if ( seq_memory[i] == seq2 || alt_seq_memory[i] == seq4 ){
                 
                  bad  = true;
                 
                  break;
                }
              }
             
              if ( !bad ){
               
                seq_memory[seq_memory_pos]      = seq2;
               
                alt_seq_memory[seq_memory_pos++= seq4;
               
                if ( seq_memory_pos == MAX_SEQ_MEMORY ){
                 
                  seq_memory_pos = 0;
                }
               
                return( new int[]{ seq1, seq2, seq3, seq4 });
              }
            }
          }
        }
        }
       
    protected boolean
        isValidAlterativeSequence(
          int    seq )
    {
      for (int i=0;i<MAX_SEQ_MEMORY;i++){
       
        if ( alt_seq_memory[i] == seq ){
         
          return( true );
        }
      }
     
      return( false );
        }
  }
}
TOP

Related Classes of com.aelitis.azureus.core.networkmanager.impl.udp.UDPConnectionSet$SequenceGenerator

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.