Package com.aelitis.azureus.core.dht.transport.udp.impl

Source Code of com.aelitis.azureus.core.dht.transport.udp.impl.DHTTransportUDPImpl

/*
* Created on 21-Jan-2005
* Created by Paul Gardner
* Copyright (C) 2004, 2005, 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.dht.transport.udp.impl;

import java.io.*;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.*;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.ipfilter.IpFilter;
import org.gudy.azureus2.core3.ipfilter.IpFilterManagerFactory;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.AERunnable;
import org.gudy.azureus2.core3.util.AESemaphore;
import org.gudy.azureus2.core3.util.AEThread2;
import org.gudy.azureus2.core3.util.Average;
import org.gudy.azureus2.core3.util.ByteFormatter;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.DelayedEvent;
import org.gudy.azureus2.core3.util.HashWrapper;
import org.gudy.azureus2.core3.util.RandomUtils;
import org.gudy.azureus2.core3.util.SimpleTimer;
import org.gudy.azureus2.core3.util.SystemProperties;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.core3.util.TimerEvent;
import org.gudy.azureus2.core3.util.TimerEventPerformer;

import com.aelitis.azureus.core.dht.DHT;
import com.aelitis.azureus.core.dht.DHTLogger;
import com.aelitis.azureus.core.dht.impl.DHTLog;
import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPosition;
import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPositionManager;
import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPositionProvider;
import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPositionProviderListener;
import com.aelitis.azureus.core.dht.transport.*;
import com.aelitis.azureus.core.dht.transport.udp.*;
import com.aelitis.azureus.core.dht.transport.udp.impl.packethandler.DHTUDPPacketHandler;
import com.aelitis.azureus.core.dht.transport.udp.impl.packethandler.DHTUDPPacketHandlerException;
import com.aelitis.azureus.core.dht.transport.udp.impl.packethandler.DHTUDPPacketHandlerFactory;
import com.aelitis.azureus.core.dht.transport.udp.impl.packethandler.DHTUDPPacketReceiver;
import com.aelitis.azureus.core.dht.transport.udp.impl.packethandler.DHTUDPRequestHandler;
import com.aelitis.azureus.core.dht.transport.util.DHTTransportRequestCounter;
import com.aelitis.azureus.core.util.average.AverageFactory;
import com.aelitis.azureus.core.util.average.MovingImmediateAverage;
import com.aelitis.azureus.core.util.bloom.BloomFilter;
import com.aelitis.azureus.core.util.bloom.BloomFilterFactory;
import com.aelitis.net.udp.uc.PRUDPPacketHandler;

/**
* @author parg
*
*/

public class
DHTTransportUDPImpl
  implements DHTTransportUDP, DHTUDPRequestHandler
{
  public static boolean TEST_EXTERNAL_IP  = false;
 
  public static final int  TRANSFER_QUEUE_MAX      = 64;
  public static final long MAX_TRANSFER_QUEUE_BYTES  = 8*1024*1024;  
   
  public static final long  WRITE_XFER_RESEND_DELAY    = 12500;
  public static final long  READ_XFER_REREQUEST_DELAY  = 5000;
  public static final long  WRITE_REPLY_TIMEOUT      = 60000;   
 
  public static final int    MIN_ADDRESS_CHANGE_PERIOD_INIT_DEFAULT  = 5*60*1000;
  public static final int    MIN_ADDRESS_CHANGE_PERIOD_NEXT_DEFAULT  = 10*60*1000;
 
  public static final int    STORE_TIMEOUT_MULTIPLIER = 2;
 
  private static boolean  XFER_TRACE  = false;
 
  static{
    if ( XFER_TRACE ){
      System.out.println( "**** DHTTransportUDPImpl xfer trace on ****" );
    }
  } 
 
 
  private String        external_address;
  private int          min_address_change_period = MIN_ADDRESS_CHANGE_PERIOD_INIT_DEFAULT;
 
  private byte        protocol_version;
  private int          network;
  private boolean        v6;
  private String        ip_override;
  private int          port;
  private int          max_fails_for_live;
  private int          max_fails_for_unknown;
  private long        request_timeout;
  private long        store_timeout;
  private boolean        reachable;
  private boolean        reachable_accurate;
  private int          dht_send_delay;
  private int          dht_receive_delay;

  private DHTLogger      logger;
   
  private DHTUDPPacketHandler      packet_handler;
 
  private DHTTransportRequestHandler  request_handler;
 
  private DHTTransportUDPContactImpl    local_contact;
 
  private Map transfer_handlers   = new HashMap();
  private Map  read_transfers    = new HashMap();
  private Map write_transfers    = new HashMap();
 
  private int   active_write_queue_processor_count;
  private long  total_bytes_on_transfer_queues;
 
  private Map  call_transfers    = new HashMap();
 
  private long last_address_change;
 
  private List listeners  = new ArrayList();
 
  private IpFilter  ip_filter  = IpFilterManagerFactory.getSingleton().getIPFilter();

 
  private DHTTransportUDPStatsImpl  stats;

  private boolean    bootstrap_node  = false;
 
 
  private static final int CONTACT_HISTORY_MAX     = 32;
  private static final int CONTACT_HISTORY_PING_SIZE  = 24;
 
  private Map<InetSocketAddress,DHTTransportContact>  contact_history =
    new LinkedHashMap<InetSocketAddress,DHTTransportContact>(CONTACT_HISTORY_MAX,0.75f,true)
    {
      protected boolean
      removeEldestEntry(
           Map.Entry<InetSocketAddress,DHTTransportContact> eldest)
      {
        return size() > CONTACT_HISTORY_MAX;
      }
    };
   
  private static final int ROUTABLE_CONTACT_HISTORY_MAX     = 128;

  private Map<InetSocketAddress,DHTTransportContact>  routable_contact_history =
    new LinkedHashMap<InetSocketAddress,DHTTransportContact>(ROUTABLE_CONTACT_HISTORY_MAX,0.75f,true)
    {
      protected boolean
      removeEldestEntry(
           Map.Entry<InetSocketAddress,DHTTransportContact> eldest)
      {
        return size() > ROUTABLE_CONTACT_HISTORY_MAX;
      }
    };
     
   
  private long          other_routable_total;
  private long          other_non_routable_total;
  private MovingImmediateAverage  routeable_percentage_average = AverageFactory.MovingImmediateAverage(8);
 
  private static final int RECENT_REPORTS_HISTORY_MAX = 32;

  private Map  recent_reports =
      new LinkedHashMap(RECENT_REPORTS_HISTORY_MAX,0.75f,true)
      {
        protected boolean
        removeEldestEntry(
             Map.Entry eldest)
        {
          return size() > RECENT_REPORTS_HISTORY_MAX;
        }
      };
   
     
  private static final int  STATS_PERIOD    = 60*1000;
  private static final int   STATS_DURATION_SECS  = 600;      // 10 minute average
  private static final long  STATS_INIT_PERIOD  = 15*60*1000// bit more than 10 mins to allow average to establish
 
  private long  stats_start_time  = SystemTime.getCurrentTime();
  private long  last_alien_count;
  private long  last_alien_fv_count;
 
  private Average  alien_average     = Average.getInstance(STATS_PERIOD,STATS_DURATION_SECS);
  private Average  alien_fv_average   = Average.getInstance(STATS_PERIOD,STATS_DURATION_SECS);
   
  private Random        random;
 
  private static final int  BAD_IP_BLOOM_FILTER_SIZE  = 32000;
  private BloomFilter      bad_ip_bloom_filter;
 
  private static AEMonitor  class_mon  = new AEMonitor( "DHTTransportUDP:class" );
 
  private AEMonitor  this_mon  = new AEMonitor( "DHTTransportUDP" );

  private boolean    initial_address_change_deferred;
  private boolean    address_changing;
 
  public
  DHTTransportUDPImpl(
    byte      _protocol_version,
    int        _network,
    boolean      _v6,
    String      _ip,
    String      _default_ip,
    int        _port,
    int        _max_fails_for_live,
    int        _max_fails_for_unknown,
    long      _timeout,
    int        _dht_send_delay,
    int        _dht_receive_delay,
    boolean      _bootstrap_node,
    boolean      _initial_reachability,
    DHTLogger    _logger )
 
    throws DHTTransportException
  {
    protocol_version    = _protocol_version;
    network          = _network;
    v6            = _v6;
    ip_override        = _ip;
    port          = _port;
    max_fails_for_live    = _max_fails_for_live;
    max_fails_for_unknown  = _max_fails_for_unknown;
    request_timeout      = _timeout;
    dht_send_delay      = _dht_send_delay;
    dht_receive_delay    = _dht_receive_delay;
    bootstrap_node      = _bootstrap_node;
    reachable        = _initial_reachability;
    logger          = _logger;
       
    store_timeout      = request_timeout * STORE_TIMEOUT_MULTIPLIER;
   
    try{
      random = RandomUtils.SECURE_RANDOM;
     
    }catch( Throwable e ){
     
      random  = new Random();
     
      logger.log( e );
    }
   
    int last_pct = COConfigurationManager.getIntParameter( "dht.udp.net" + network + ".routeable_pct", -1 );
   
    if ( last_pct > 0 ){
     
      routeable_percentage_average.update( last_pct );
    }
   
    createPacketHandler();
   
    SimpleTimer.addPeriodicEvent(
      "DHTUDP:stats",
      STATS_PERIOD,
      new TimerEventPerformer()
      {
        private int tick_count;
       
        public void
        perform(
          TimerEvent  event )
        {
          updateStats( tick_count++);
        }
      });
   
    String  default_ip = _default_ip==null?(v6?"::1":"127.0.0.1"):_default_ip;
       
    getExternalAddress( default_ip, logger );
   
    InetSocketAddress  address = new InetSocketAddress( external_address, port );

    DHTNetworkPositionManager.addProviderListener(
      new DHTNetworkPositionProviderListener()
      {
        public void
        providerAdded(
          DHTNetworkPositionProvider    provider )
        {
          if ( local_contact != null ){
           
            local_contact.createNetworkPositions( true );
           
            try{
              this_mon.enter();

              for ( DHTTransportContact c: contact_history.values()){
               
                c.createNetworkPositions( false );
              }

              for ( DHTTransportContact c: routable_contact_history.values()){
               
                c.createNetworkPositions( false );
              }
            }finally{
             
              this_mon.exit();
            }
           
            for (int i=0;i<listeners.size();i++){
             
              try{
                ((DHTTransportListener)listeners.get(i)).resetNetworkPositions();
               
              }catch( Throwable e ){
               
                Debug.printStackTrace(e);
              }
            }
          }
        }
       
        public void
        providerRemoved(
          DHTNetworkPositionProvider    provider )
        {       
        }
      });
   
    logger.log( "Initial external address: " + address );
   
    local_contact = new DHTTransportUDPContactImpl( true, this, address, address, protocol_version, random.nextInt(), 0 );
  }
 
  protected void
  createPacketHandler()
 
    throws DHTTransportException
  {
    DHTUDPPacketHelper.registerCodecs();

    // DHTPRUDPPacket relies on the request-handler being an instanceof THIS so watch out
    // if you change it :)
 
    try{
      if ( packet_handler != null ){
       
        packet_handler.destroy();
      }
     
      packet_handler = DHTUDPPacketHandlerFactory.getHandler( this, this );
     
    }catch( Throwable e ){
     
      throw( new DHTTransportException( "Failed to get packet handler", e ));
    }
 
      // limit send and receive rates. Receive rate is lower as we want a stricter limit
      // on the max speed we generate packets than those we're willing to process.
   
    // logger.log( "send delay = " + _dht_send_delay + ", recv = " + _dht_receive_delay );
   
    packet_handler.setDelays( dht_send_delay, dht_receive_delay, (int)request_timeout );
   
    stats_start_time  = SystemTime.getCurrentTime();
   
    if ( stats == null ){
     
      stats =  new DHTTransportUDPStatsImpl( this, protocol_version, packet_handler.getStats());
     
    }else{
     
      stats.setStats( packet_handler.getStats());
    }
  }
 
  protected void
  updateStats(
    int  tick_count )
  {
    long  alien_count  = 0;
   
    long[]  aliens = stats.getAliens();
   
    for (int i=0;i<aliens.length;i++){
     
      alien_count  += aliens[i];
    }
   
    long  alien_fv_count = aliens[ DHTTransportStats.AT_FIND_VALUE ];
   
    alien_average.addValue( (alien_count-last_alien_count)*STATS_PERIOD/1000);
    alien_fv_average.addValue( (alien_fv_count-last_alien_fv_count)*STATS_PERIOD/1000);

    last_alien_count  = alien_count;
    last_alien_fv_count  = alien_fv_count;
   
    long  now = SystemTime.getCurrentTime();
   
    if ( now <   stats_start_time ){
     
      stats_start_time  = now;
     
    }else{
     
        // only fiddle with the initial view of reachability when things have had
        // time to stabilise
     
      if ( Constants.isCVSVersion()){
             
        long fv_average     = alien_fv_average.getAverage();
        long all_average     = alien_average.getAverage();
     
        logger.log( "Aliens for net " + network + ": " + fv_average + "/" + all_average );
      }
     
      if ( now - stats_start_time > STATS_INIT_PERIOD ){
       
        reachable_accurate  = true;
       
        boolean  old_reachable  = reachable;
       
        if ( alien_fv_average.getAverage() > 1 ){
         
          reachable  = true;
         
        }else if ( alien_average.getAverage() > 3 ){
         
          reachable  = true;
         
        }else{
         
          reachable  = false;
        }
       
        if ( old_reachable != reachable ){
         
          for (int i=0;i<listeners.size();i++){
           
            try{
              ((DHTTransportListener)listeners.get(i)).reachabilityChanged( reachable );
             
            }catch( Throwable e ){
             
              Debug.printStackTrace(e);
            }
          } 
        }
      }
    }
   
    int  pct = getRouteablePercentage();
     
    if ( pct > 0 ){
     
      COConfigurationManager.setParameter("dht.udp.net" + network + ".routeable_pct", pct );
    }
   
    // System.out.println( "routables=" + other_routable_total + ", non=" + other_non_routable_total );
   
    // System.out.println( "net " + network + ": aliens = " + alien_average.getAverage() + ", alien fv = " + alien_fv_average.getAverage());
  }
 
  protected void
  recordSkew(
    InetSocketAddress  originator_address,
    long        skew )
  {
    if ( stats != null ){
   
      stats.recordSkew( originator_address, skew );
    }
  }
 
  protected int
  getNodeStatus()
  {
    if ( bootstrap_node ){
   
        // bootstrap node is special case and not generally routable
     
      return( 0 );
    }
   
    if ( reachable_accurate ){
     
      int  status = reachable?DHTTransportUDPContactImpl.NODE_STATUS_ROUTABLE:0;
     
      return( status );
     
    }else{
     
      return( DHTTransportUDPContactImpl.NODE_STATUS_UNKNOWN );
    }
  }
 
  public boolean
  isReachable()
  {
    return( reachable );
  }
 
  public byte
  getProtocolVersion()
  {
    return( protocol_version );
  }
 
  public int
  getPort()
  {
    return( port );
  }
 
  public void
  setPort(
    int  new_port )
 
    throws DHTTransportException
  {
    if ( new_port == port ){
     
      return;
    }
   
    port  = new_port;
   
    createPacketHandler();
   
    setLocalContact();
  }
 
  public long
  getTimeout()
  {
    return( request_timeout );
  }
 
  public void
  setTimeout(
    long    timeout )
  {
    if ( request_timeout == timeout ){
     
      return;
    }
   
    request_timeout = timeout;
    store_timeout   = request_timeout * STORE_TIMEOUT_MULTIPLIER;
   
    packet_handler.setDelays( dht_send_delay, dht_receive_delay, (int)request_timeout );
  }
 
  public int
  getNetwork()
  {
    return( network );
  }
 
  public void
  testInstanceIDChange()
 
    throws DHTTransportException
  {
    local_contact = new DHTTransportUDPContactImpl( true, this, local_contact.getTransportAddress(), local_contact.getExternalAddress(), protocol_version, random.nextInt(), 0);   
  }
 
  public void
  testTransportIDChange()
 
    throws DHTTransportException
  {
    if ( external_address.equals("127.0.0.1")){
     
      external_address = "192.168.0.2";
    }else{
     
      external_address = "127.0.0.1";
    }
   
    InetSocketAddress  address = new InetSocketAddress( external_address, port );
   
    local_contact = new DHTTransportUDPContactImpl( true, this, address, address, protocol_version, local_contact.getInstanceID(), 0 );   

    for (int i=0;i<listeners.size();i++){
     
      try{
        ((DHTTransportListener)listeners.get(i)).localContactChanged( local_contact );
       
      }catch( Throwable e ){
       
        Debug.printStackTrace(e);
      }
    }
  }
 
  public void
  testExternalAddressChange()
  {
    try{
      Iterator  it = contact_history.values().iterator();
     
      DHTTransportUDPContactImpl c1 = (DHTTransportUDPContactImpl)it.next();
      DHTTransportUDPContactImpl c2 = (DHTTransportUDPContactImpl)it.next();
     
      externalAddressChange( c1, c2.getExternalAddress(), true );
      //externalAddressChange( c, new InetSocketAddress( "192.168.0.7", 6881 ));
     
    }catch( Throwable e ){
     
      Debug.printStackTrace(e);
    }
  }
 
  public void
  testNetworkAlive(
    boolean    alive )
  {
    packet_handler.testNetworkAlive( alive );
  }
 
  protected void
  getExternalAddress(
    String        default_address,
    final DHTLogger    log )
  {
      // class level synchronisation is for testing purposes when running multiple UDP instances
      // in the same VM
   
    try{
      class_mon.enter();
     
      String new_external_address = null;
     
      try{       
        log.log( "Obtaining external address" );
       
        if ( TEST_EXTERNAL_IP ){
         
          new_external_address  = v6?"::1":"127.0.0.1";
         
          log.log( "    External IP address obtained from test data: " + new_external_address );
        }
         
        if ( ip_override != null ){
         
          new_external_address  = ip_override;
         
          log.log( "    External IP address explicitly overridden: " + new_external_address );
        }
       
        if ( new_external_address == null ){

            // First attempt is via other contacts we know about. Select three
         
          List  contacts;
         
          try{
            this_mon.enter();
           
            contacts = new ArrayList( contact_history.values());
           
          }finally{
           
            this_mon.exit();
          }
         
            // randomly select a number of entries to ping until we
            // get three replies
         
          String  returned_address   = null;
          int    returned_matches  = 0;
         
          int    search_lim = Math.min( CONTACT_HISTORY_PING_SIZE, contacts.size());
         
          log.log( "    Contacts to search = " + search_lim );
         
          for (int i=0;i<search_lim;i++){
           
            DHTTransportUDPContactImpl  contact = (DHTTransportUDPContactImpl)contacts.remove((int)(contacts.size()*Math.random()));
                         
            InetSocketAddress a = askContactForExternalAddress( contact );
           
            if ( a != null && a.getAddress() != null ){
             
              String  ip = a.getAddress().getHostAddress();
             
              if ( returned_address == null ){
               
                returned_address = ip;
                                 
                log.log( "    : contact " + contact.getString() + " reported external address as '" + ip + "'" );
               
                returned_matches++;
               
              }else if ( returned_address.equals( ip )){
               
                returned_matches++;
               
                log.log( "    : contact " + contact.getString() + " also reported external address as '" + ip + "'" );
               
                if ( returned_matches == 3 ){
                 
                  new_external_address  = returned_address;
                 
                  log.log( "    External IP address obtained from contacts: "  + returned_address );
                 
                  break;
                }
              }else{
               
                log.log( "    : contact " + contact.getString() + " reported external address as '" + ip + "', abandoning due to mismatch" );
               
                  // mismatch - give up
               
                break;
              }
            }else{
             
              log.log( "    : contact " + contact.getString() + " didn't reply" );
            }
          }

        }
       
        if ( new_external_address == null ){
     
          InetAddress public_address = logger.getPluginInterface().getUtilities().getPublicAddress( v6 );
                     
          if ( public_address != null ){
             
            new_external_address = public_address.getHostAddress();
           
            log.log( "    External IP address obtained: " + new_external_address );
          }
        }
       
      }catch( Throwable e ){
       
        Debug.printStackTrace( e );
      }
   
      if ( new_external_address == null ){
     
        new_external_address =  default_address;
     
        log.log( "    External IP address defaulted:  " + new_external_address );
      }
     
      if ( external_address == null || !external_address.equals( new_external_address )){
       
        informLocalAddress( new_external_address );
      }
     
      external_address = new_external_address;
     
    }finally{
     
      class_mon.exit();
    }
  }
 
  protected void
  informLocalAddress(
    String  address )
  {
    for (int i=0;i<listeners.size();i++){
     
      try{
        ((DHTTransportListener)listeners.get(i)).currentAddress( address );
       
      }catch( Throwable e ){
       
        Debug.printStackTrace(e);
      }
    }   
  }
   
  protected void
  externalAddressChange(
    final DHTTransportUDPContactImpl  reporter,
    final InetSocketAddress        new_address,
    boolean                force )
 
    throws DHTTransportException
  {
      /*
       * A node has reported that our external address and the one he's seen a
       * message coming from differ. Natural explanations are along the lines of
       * 1) my address is dynamically allocated by my ISP and it has changed
       * 2) I have multiple network interfaces
       * 3) there's some kind of proxy going on
       * 4) this is a DOS attempting to stuff me up
       *
       * We assume that our address won't change more frequently than once every
       * 5 minutes
       * We assume that if we can successfully obtain an external address by
       * using the above explicit check then this is accurate
       * Only in the case where the above check fails do we believe the address
       * that we've been told about
       */
     
    InetAddress  ia = new_address.getAddress();
   
    if ( ia == null ){
     
      Debug.out( "reported new external address '" + new_address + "' is unresolved" );
     
      throw( new DHTTransportException( "Address '" + new_address + "' is unresolved" ));
    }
   
      // dump addresses incompatible with our protocol
   
    if (   ( ia instanceof Inet4Address && v6  ) ||
        ( ia instanceof Inet6Address && !v6 )){
     
        // reduce debug spam, just return
     
      // throw( new DHTTransportException( "Address " + new_address + " is incompatible with protocol family for " + external_address ));
     
      return;
    }
   
    final String  new_ip = ia.getHostAddress();
   
    if ( new_ip.equals( external_address )){
     
        // probably just be a second notification of an address change, return
        // "ok to retry" as it should now work
             
      return;
    }
   
    try{
      this_mon.enter();
 
      long  now = SystemTime.getCurrentTime();
 
      if ( now - last_address_change < min_address_change_period ){
       
        return;
      }
           
      if ( contact_history.size() < CONTACT_HISTORY_MAX && !force ){
       
        if ( !initial_address_change_deferred ){
         
          initial_address_change_deferred = true;
         
          logger.log( "Node " + reporter.getString() + " has reported that the external IP address is '" + new_address + "': deferring new checks" );

          new DelayedEvent(
            "DHTTransportUDP:delayAC",
            30*1000,
            new AERunnable()
            {
              public void
              runSupport()
              {
                try{
                  externalAddressChange( reporter, new_address, true );
                 
                }catch( Throwable e ){
                 
                }
              }
            });
        }
       
        return;
      }
     
      logger.log( "Node " + reporter.getString() + " has reported that the external IP address is '" + new_address + "'" );
 
        // check for dodgy addresses that shouldn't appear as an external address!
     
      if ( invalidExternalAddress( ia )){
       
        logger.log( "     This is invalid as it is a private address." );
 
        return;
      }
 
        // another situation to ignore is where the reported address is the same as
        // the reporter (they must be seeing it via, say, socks connection on a local
        // interface
     
      if ( reporter.getExternalAddress().getAddress().getHostAddress().equals( new_ip )){
       
        logger.log( "     This is invalid as it is the same as the reporter's address." );
 
        return;   
      }
     
      last_address_change  = now;
     
        // bump up min period for subsequent changes
     
      if ( min_address_change_period == MIN_ADDRESS_CHANGE_PERIOD_INIT_DEFAULT ){
       
        min_address_change_period = MIN_ADDRESS_CHANGE_PERIOD_NEXT_DEFAULT;
      }
    }finally{
     
      this_mon.exit();
    }
   
    final String  old_external_address = external_address;
   
      // we need to perform this test on a separate thread otherwise we'll block in the UDP handling
      // code because we're already running on the "process" callback from the UDP handler
      // (the test attempts to ping contacts)
     
   
    new AEThread2( "DHTTransportUDP:getAddress", true )
    {
      public void
      run()
      {
        try{
          this_mon.enter();

          if ( address_changing ){
           
            return;
          }
         
          address_changing  = true;
         
        }finally{
         
          this_mon.exit();
        }
         
        try{
          getExternalAddress( new_ip, logger );
         
          if ( old_external_address.equals( external_address )){
           
              // address hasn't changed, notifier must be perceiving different address
              // due to proxy or something
                   
            return;
          }
         
          setLocalContact();
         
        }finally{
         
          try{
            this_mon.enter();

            address_changing  = false;
           
          }finally{
           
            this_mon.exit();
          }
        }
      }
    }.start();
  }
 
  protected void
  contactAlive(
    DHTTransportUDPContactImpl  contact )
  {
    try{
      this_mon.enter();
     
      contact_history.put( contact.getTransportAddress(), contact );
     
    }finally{
     
      this_mon.exit();
    }
  }
 
  public DHTTransportContact[]
     getReachableContacts()
     {
    try{
      this_mon.enter();
     
      Collection<DHTTransportContact> vals = routable_contact_history.values();
     
      DHTTransportContact[]  res = new DHTTransportContact[vals.size()];
     
      vals.toArray( res );
     
      return( res );
     
    }finally{
     
      this_mon.exit();
    }
     }
 
  public DHTTransportContact[]
     getRecentContacts()
     {
    try{
      this_mon.enter();
     
      Collection<DHTTransportContact> vals = contact_history.values();
     
      DHTTransportContact[]  res = new DHTTransportContact[vals.size()];
     
      vals.toArray( res );
     
      return( res );
     
    }finally{
     
      this_mon.exit();
    }
     }
 
  protected void
  updateContactStatus(
    DHTTransportUDPContactImpl  contact,
    int              status,
    boolean            incoming )
  {
    try{
      this_mon.enter();
       
      contact.setNodeStatus( status );

      if ( contact.getProtocolVersion() >= DHTTransportUDP.PROTOCOL_VERSION_XFER_STATUS ){
               
        if ( status != DHTTransportUDPContactImpl.NODE_STATUS_UNKNOWN ){
         
          boolean  other_routable = (status & DHTTransportUDPContactImpl.NODE_STATUS_ROUTABLE) != 0;
         
            // only maintain stats on incoming requests so we get a fair sample.
            // in general we'll only get replies from routable contacts so if we
            // take this into account then everything gets skewed
         
          if ( other_routable ){
           
            if ( incoming ){
             
              other_routable_total++;
            }
                       
            routable_contact_history.put( contact.getTransportAddress(), contact );
           
          }else{
           
            if ( incoming ){
             
              other_non_routable_total++;
            }
          }
        }
      }
     
    }finally{
     
      this_mon.exit();
    }
  }
 
  public int
  getRouteablePercentage()
  {
    synchronized( routeable_percentage_average ){
   
      double  average = routeable_percentage_average.getAverage();
     
      long  both_total = other_routable_total + other_non_routable_total;
     
      int  current_percent;
     
      if ( both_total == 0 ){
       
        current_percent = 0;
       
      }else{
       
        current_percent = (int)(( other_routable_total * 100 )/ both_total );
      }
      
      if ( both_total >= 300 ){
       
          // add current to average and reset
       
        if ( current_percent > 0 ){
       
          average = routeable_percentage_average.update( current_percent );
         
          other_routable_total = other_non_routable_total = 0;
        }
      }else if ( both_total >= 100 ){
       
          // if we have enough samples and no existing average then use current
       
        if ( average == 0 ){
         
          average = current_percent;
         
        }else{
         
            // factor in current percantage
         
          int samples = routeable_percentage_average.getSampleCount();
         
          if ( samples > 0 ){
           
            average = (( samples * average ) + current_percent ) / ( samples + 1 );
          }
        }
      }
     
      int result = (int)average;
     
      if ( result == 0 ){
       
          // -1 indicates we have no usable value
       
        result = -1;
      }
     
      return( result );
    }
  }
 
  protected boolean
  invalidExternalAddress(
    InetAddress  ia )
  {
    returnia.isLinkLocalAddress() ||
        ia.isLoopbackAddress() ||
        ia.isSiteLocalAddress());
  }
 
  protected int
  getMaxFailForLiveCount()
  {
    return( max_fails_for_live );
  }
 
  protected int
  getMaxFailForUnknownCount()
  {
    return( max_fails_for_unknown );
  }
 
  public DHTTransportContact
  getLocalContact()
  {
    return( local_contact );
  }
 
  protected void
  setLocalContact()
  {
    InetSocketAddress  s_address = new InetSocketAddress( external_address, port );
   
    try{
      local_contact = new DHTTransportUDPContactImpl( true, DHTTransportUDPImpl.this, s_address, s_address, protocol_version, random.nextInt(), 0);
 
      logger.log( "External address changed: " + s_address );
     
      Debug.out( "DHTTransport: address changed to " + s_address );
     
      for (int i=0;i<listeners.size();i++){
       
        try{
          ((DHTTransportListener)listeners.get(i)).localContactChanged( local_contact );
         
        }catch( Throwable e ){
         
          Debug.printStackTrace(e);
        }
      }
    }catch( Throwable e ){
     
      Debug.printStackTrace(e);
    }
  }
 
  public DHTTransportContact
  importContact(
    DataInputStream    is )
 
    throws IOException, DHTTransportException
  {
    DHTTransportUDPContactImpl  contact = DHTUDPUtils.deserialiseContact( this, is );
       
    importContact( contact );
     
    return( contact );
  }
 
  public DHTTransportUDPContact
  importContact(
    InetSocketAddress  _address,
    byte        _protocol_version )
 
    throws DHTTransportException
  {
      // instance id of 0 means "unknown"
   
    DHTTransportUDPContactImpl  contact = new DHTTransportUDPContactImpl( false, this, _address, _address, _protocol_version, 0, 0 );
   
    importContact( contact );

    return( contact );
  }
 
  protected void
  importContact(
    DHTTransportUDPContactImpl  contact )
  {
    try{
      this_mon.enter();
     
        // consider newly imported contacts as potential contacts for IP address queries if we've
        // got space (in particular, at start of day we may be able to get an address off these if
        // they're still alive )
     
      if ( contact_history.size() < CONTACT_HISTORY_MAX ){
       
        contact_history.put( contact.getTransportAddress(), contact );
      }
     
    }finally{
     
      this_mon.exit();
    }
   
    request_handler.contactImported( contact );
   
    // logger.log( "Imported contact " + contact.getString());
  }
 
  public void
  exportContact(
    DHTTransportContact  contact,
    DataOutputStream  os )
 
    throws IOException, DHTTransportException
  {
    DHTUDPUtils.serialiseContact( os, contact );
  }
 
  public void
  removeContact(
    DHTTransportContact  contact )
  {
    request_handler.contactRemoved( contact );
  }
 
  public void
  setRequestHandler(
    DHTTransportRequestHandler  _request_handler )
  {
    request_handler = new DHTTransportRequestCounter( _request_handler, stats );
  }
 
  public DHTTransportStats
  getStats()
  {
    return( stats );
  }
 
  //protected HashMap  port_map = new HashMap();
  //protected long    last_portmap_dump  = SystemTime.getCurrentTime();
 
  protected void
  checkAddress(
    DHTTransportUDPContactImpl    contact )
 
    throws DHTUDPPacketHandlerException
  {
    /*
    int  port = contact.getExternalAddress().getPort();
   
    try{
      this_mon.enter();
   
      int  count;
     
      Integer i = (Integer)port_map.get(new Integer(port));
     
      if ( i != null ){
       
        count   = i.intValue() + 1;
       
      }else{
       
        count  = 1;
      }
     
      port_map.put( new Integer(port), new Integer(count));
     
      long  now = SystemTime.getCurrentTime();
     
      if ( now - last_portmap_dump > 60000 ){
       
        last_portmap_dump  = now;
       
        Iterator  it = port_map.keySet().iterator();
       
        Map  rev = new TreeMap();
       
        while( it.hasNext()){
         
          Integer  key = (Integer)it.next();
         
          Integer  val = (Integer)port_map.get(key);
         
          rev.put( val, key );
        }
       
        it = rev.keySet().iterator();
       
        while( it.hasNext()){
         
          Integer  val = (Integer)it.next();
         
          Integer  key = (Integer)rev.get(val);
         
          System.out.println( "port:" + key + "->" + val );
        }
      }
     
    }finally{
     
      this_mon.exit();
    }
    */
   
    if ( ip_filter.isEnabled()){
     
        // don't need to synchronize access to the bloom filter as it works fine
        // without protection (especially as its add only)
     
      byte[]  addr = contact.getTransportAddress().getAddress().getAddress();
     
      if ( bad_ip_bloom_filter == null ){
       
        bad_ip_bloom_filter = BloomFilterFactory.createAddOnly( BAD_IP_BLOOM_FILTER_SIZE );
       
      }else{
       
        if ( bad_ip_bloom_filter.contains( addr )){
         
          throw( new DHTUDPPacketHandlerException( "IPFilter check fails (repeat)" ));
        }
      }
     
      if ( ip_filter.isInRange(
          contact.getTransportAddress().getAddress(), "DHT", null,
          logger.isEnabled( DHTLogger.LT_IP_FILTER ))){
       
          // don't let an attacker deliberately fill up our filter so we start
          // rejecting valid addresses
       
        if ( bad_ip_bloom_filter.getEntryCount() >= BAD_IP_BLOOM_FILTER_SIZE/10 ){
         
          bad_ip_bloom_filter = BloomFilterFactory.createAddOnly( BAD_IP_BLOOM_FILTER_SIZE );
        }
       
        bad_ip_bloom_filter.add( addr );
       
        throw( new DHTUDPPacketHandlerException( "IPFilter check fails" ));
      }
    }
  }
 
  protected void
  sendPing(
    final DHTTransportUDPContactImpl  contact,
    final DHTTransportReplyHandler    handler,
    long                timeout,
    int                  priority )
  {
    try{
      checkAddress( contact );
     
      final long  connection_id = getConnectionID();     

      final DHTUDPPacketRequestPing  request =
        new DHTUDPPacketRequestPing( this, connection_id, local_contact, contact );
       
      stats.pingSent( request );

      requestSendRequestProcessor( contact, request );
     
      packet_handler.sendAndReceive(
        request,
        contact.getTransportAddress(),
        new DHTUDPPacketReceiver()
        {
          public void
          packetReceived(
            DHTUDPPacketReply  packet,
            InetSocketAddress  from_address,
            long        elapsed_time )
          {
            try{             
              if ( packet.getConnectionId() != connection_id ){
               
                throw( new Exception( "connection id mismatch" ));
              }
             
              contact.setInstanceIDAndVersion( packet.getTargetInstanceID(), packet.getProtocolVersion());
             
              requestSendReplyProcessor( contact, handler, packet, elapsed_time );             
               
              stats.pingOK();
             
              handler.pingReply( contact, (int)elapsed_time );
           
            }catch( DHTUDPPacketHandlerException e ){
             
              error( e );
             
            }catch( Throwable e ){
             
              Debug.printStackTrace(e);
             
              error( new DHTUDPPacketHandlerException( "ping failed", e ));
            }
          }
         
          public void
          error(
            DHTUDPPacketHandlerException  e )
          {
            stats.pingFailed();
           
            handler.failed( contact,e );
          }
        },
        timeout, priority );
     
    }catch( Throwable e ){
     
      stats.pingFailed();
     
      handler.failed( contact,e );
    }
  }
 
  protected void
  sendPing(
    final DHTTransportUDPContactImpl  contact,
    final DHTTransportReplyHandler    handler )
  {
    sendPing( contact, handler, request_timeout, PRUDPPacketHandler.PRIORITY_MEDIUM );
  }

  protected void
  sendImmediatePing(
    final DHTTransportUDPContactImpl  contact,
    final DHTTransportReplyHandler    handler,
    long                timeout )
  {
    sendPing( contact, handler, timeout, PRUDPPacketHandler.PRIORITY_IMMEDIATE );
  }
 
  protected void
  sendKeyBlockRequest(
    final DHTTransportUDPContactImpl  contact,
    final DHTTransportReplyHandler    handler,
    byte[]                block_request,
    byte[]                block_signature )
  {
    try{
      checkAddress( contact );
     
      final long  connection_id = getConnectionID();     

      final DHTUDPPacketRequestKeyBlock  request =
        new DHTUDPPacketRequestKeyBlock( this, connection_id, local_contact, contact );
     
      request.setKeyBlockDetails( block_request, block_signature );
     
      stats.keyBlockSent( request );

      request.setRandomID( contact.getRandomID());

      requestSendRequestProcessor( contact, request );
     
      packet_handler.sendAndReceive(
        request,
        contact.getTransportAddress(),
        new DHTUDPPacketReceiver()
        {
          public void
          packetReceived(
            DHTUDPPacketReply  packet,
            InetSocketAddress  from_address,
            long        elapsed_time )
          {
            try{             
              if ( packet.getConnectionId() != connection_id ){
               
                throw( new Exception( "connection id mismatch" ));
              }
             
              contact.setInstanceIDAndVersion( packet.getTargetInstanceID(), packet.getProtocolVersion());
             
              requestSendReplyProcessor( contact, handler, packet, elapsed_time );             
               
              stats.keyBlockOK();
             
              handler.keyBlockReply( contact );
           
            }catch( DHTUDPPacketHandlerException e ){
             
              error( e );
             
            }catch( Throwable e ){
             
              Debug.printStackTrace(e);
             
              error( new DHTUDPPacketHandlerException( "send key block failed", e ));
            }
          }
         
          public void
          error(
            DHTUDPPacketHandlerException  e )
          {
            stats.keyBlockFailed();
           
            handler.failed( contact,e );
          }
        },
        request_timeout, PRUDPPacketHandler.PRIORITY_MEDIUM );
     
    }catch( Throwable e ){
     
      stats.keyBlockFailed();
     
      handler.failed( contact,e );
    }
  }
 
    // stats
 
  protected void
  sendStats(
    final DHTTransportUDPContactImpl  contact,
    final DHTTransportReplyHandler    handler )
  {
    try{
      checkAddress( contact );
     
      final long  connection_id = getConnectionID();
     
      final DHTUDPPacketRequestStats  request =
        new DHTUDPPacketRequestStats( this, connection_id, local_contact, contact );
       
      // request.setStatsType( DHTUDPPacketRequestStats.STATS_TYPE_NP_VER2 );
     
      stats.statsSent( request );

      requestSendRequestProcessor( contact, request );

      packet_handler.sendAndReceive(
        request,
        contact.getTransportAddress(),
        new DHTUDPPacketReceiver()
        {         
          public void
          packetReceived(
            DHTUDPPacketReply  packet,
            InetSocketAddress  from_address,
            long        elapsed_time )
          {
            try{             
              if ( packet.getConnectionId() != connection_id ){
               
                throw( new Exception( "connection id mismatch" ));
              }
             
              contact.setInstanceIDAndVersion( packet.getTargetInstanceID(), packet.getProtocolVersion());
             
              requestSendReplyProcessor( contact, handler, packet, elapsed_time );
                   
              DHTUDPPacketReplyStats  reply = (DHTUDPPacketReplyStats)packet;

              stats.statsOK();
             
              if ( reply.getStatsType() == DHTUDPPacketRequestStats.STATS_TYPE_ORIGINAL ){
               
                handler.statsReply( contact, reply.getOriginalStats());
              }else{
               
                // currently no handler for new stats
               
                System.out.println( "new stats reply:" + reply.getString());
              }
           
            }catch( DHTUDPPacketHandlerException e ){
             
              error( e );
             
            }catch( Throwable e ){
             
              Debug.printStackTrace(e);
             
              error( new DHTUDPPacketHandlerException( "stats failed", e ));
            }
          }
         
          public void
          error(
            DHTUDPPacketHandlerException  e )
          {
            stats.statsFailed();
           
            handler.failed( contact, e );
          }
        },
        request_timeout, PRUDPPacketHandler.PRIORITY_LOW );
     
    }catch( Throwable e ){
     
      stats.statsFailed();
     
      handler.failed( contact, e );
    }
  }
 
    // PING for deducing external IP address
 
  protected InetSocketAddress
  askContactForExternalAddress(
    DHTTransportUDPContactImpl  contact )
  {
    try{
      checkAddress( contact );
   
      final long  connection_id = getConnectionID();
     
      final DHTUDPPacketRequestPing  request =
          new DHTUDPPacketRequestPing( this, connection_id, local_contact, contact );
       
      stats.pingSent( request );

      final AESemaphore  sem = new AESemaphore( "DHTTransUDP:extping" );

      final InetSocketAddress[]  result = new InetSocketAddress[1];
     
      packet_handler.sendAndReceive(
        request,
        contact.getTransportAddress(),
        new DHTUDPPacketReceiver()
        {
          public void
          packetReceived(
            DHTUDPPacketReply  _packet,
            InetSocketAddress  from_address,
            long        elapsed_time )
          {
            try{
             
              if ( _packet instanceof DHTUDPPacketReplyPing ){
               
                // ping was OK so current address is OK
               
                result[0] = local_contact.getExternalAddress();
               
              }else if ( _packet instanceof DHTUDPPacketReplyError ){
               
                DHTUDPPacketReplyError  packet = (DHTUDPPacketReplyError)_packet;
               
                if ( packet.getErrorType() == DHTUDPPacketReplyError.ET_ORIGINATOR_ADDRESS_WRONG ){
                 
                  result[0] = packet.getOriginatingAddress();
                }
              }
            }finally{
             
              sem.release();
            }
          }
         
          public void
          error(
            DHTUDPPacketHandlerException  e )
          {
            try{
              stats.pingFailed();
             
            }finally{
           
              sem.release();
            }
          }
        },
        5000, PRUDPPacketHandler.PRIORITY_HIGH );
     
      sem.reserve( 5000 );
     
      return( result[0] );
     
    }catch( Throwable e ){
     
      stats.pingFailed();

      return( null );
    }
  }
 
    // STORE
 
  public void
  sendStore(
    final DHTTransportUDPContactImpl  contact,
    final DHTTransportReplyHandler    handler,
    byte[][]              keys,
    DHTTransportValue[][]        value_sets,
    int                  priority )
  {
    final long  connection_id = getConnectionID();
   
    if ( false ){
      int  total_values = 0;
      for (int i=0;i<keys.length;i++){
        total_values += value_sets[i].length;
      }
      System.out.println( "store: keys = " + keys.length +", values = " + total_values );
    }
   
      // only report to caller the outcome of the first packet
   
    int  packet_count  = 0;
   
    try{
      checkAddress( contact );
     
      int    current_key_index  = 0;
      int    current_value_index  = 0;
     
      while( current_key_index < keys.length ){
     
        packet_count++;
               
        int  space = DHTUDPPacketHelper.PACKET_MAX_BYTES - DHTUDPPacketRequest.DHT_HEADER_SIZE;
               
        List  key_list  = new ArrayList();
        List  values_list  = new ArrayList();
               
        key_list.add( keys[current_key_index]);
       
        space -= ( keys[current_key_index].length + 1 )// 1 for length marker
       
        values_list.add( new ArrayList());
       
        while(   space > 0 && 
            current_key_index < keys.length ){
         
          if ( current_value_index == value_sets[current_key_index].length ){
           
              // all values from the current key have been processed
           
            current_key_index++;
           
            current_value_index  = 0;
           
            if ( key_list.size() == DHTUDPPacketRequestStore.MAX_KEYS_PER_PACKET ){
             
                // no more keys allowed in this packet
               
              break;
            }
             
            if ( current_key_index == keys.length ){
           
                // no more keys left, job done
             
              break;
            }
           
            key_list.add( keys[current_key_index]);
           
            space -= ( keys[current_key_index].length + 1 )// 1 for length marker
           
            values_list.add( new ArrayList());
          }
         
          DHTTransportValue  value = value_sets[current_key_index][current_value_index];
         
          int  entry_size = DHTUDPUtils.DHTTRANSPORTVALUE_SIZE_WITHOUT_VALUE + value.getValue().length + 1;
         
          List  values = (List)values_list.get(values_list.size()-1);
         
          if (   space < entry_size ||
              values.size() == DHTUDPPacketRequestStore.MAX_VALUES_PER_KEY ){
           
              // no space left or we've used up our limit on the
              // number of values permitted per key
           
            break;
          }
         
          values.add( value );
         
          space -= entry_size;
         
          current_value_index++;
        }
       
        int  packet_entries = key_list.size();

        if ( packet_entries > 0 ){
       
            // if last entry has no values then ignore it
         
          if ( ((List)values_list.get( packet_entries-1)).size() == 0 ){
           
            packet_entries--;
          }
        }
       
        if ( packet_entries == 0 ){
         
          break;
        }
 
        byte[][]        packet_keys     = new byte[packet_entries][];
        DHTTransportValue[][]  packet_value_sets   = new DHTTransportValue[packet_entries][];
       
        //int  packet_value_count = 0;
       
        for (int i=0;i<packet_entries;i++){
         
          packet_keys[i] = (byte[])key_list.get(i);
         
          List  values = (List)values_list.get(i);
         
          packet_value_sets[i] = new DHTTransportValue[values.size()];
         
          for (int j=0;j<values.size();j++){
           
            packet_value_sets[i][j] = (DHTTransportValue)values.get(j);
           
            //packet_value_count++;
          }
        }
       
        // System.out.println( "    packet " + packet_count + ": keys = " + packet_entries + ", values = " + packet_value_count );
               
       
        final DHTUDPPacketRequestStore  request =
          new DHTUDPPacketRequestStore( this, connection_id, local_contact, contact );
     
        stats.storeSent( request );

        request.setRandomID( contact.getRandomID());
       
        request.setKeys( packet_keys );
       
        request.setValueSets( packet_value_sets );
       
        final int f_packet_count  = packet_count;
       
        requestSendRequestProcessor( contact, request );

        packet_handler.sendAndReceive(
          request,
          contact.getTransportAddress(),
          new DHTUDPPacketReceiver()
          {
            public void
            packetReceived(
              DHTUDPPacketReply  packet,
              InetSocketAddress  from_address,
              long        elapsed_time )
            {
              try{               
                if ( packet.getConnectionId() != connection_id ){
                 
                  throw( new Exception( "connection id mismatch: sender=" + from_address + ",packet=" + packet.getString()));
                }
               
                contact.setInstanceIDAndVersion( packet.getTargetInstanceID(), packet.getProtocolVersion());
               
                requestSendReplyProcessor( contact, handler, packet, elapsed_time );

                DHTUDPPacketReplyStore  reply = (DHTUDPPacketReplyStore)packet;
                 
                stats.storeOK();
                 
                if ( f_packet_count == 1 ){
                   
                  handler.storeReply( contact, reply.getDiversificationTypes());
                }
               
              }catch( DHTUDPPacketHandlerException e ){
               
                error( e );
               
              }catch( Throwable e ){
               
                Debug.printStackTrace(e);
               
                error( new DHTUDPPacketHandlerException( "store failed", e ));
              }
            }
           
            public void
            error(
              DHTUDPPacketHandlerException  e )
            {
              stats.storeFailed();
             
              if ( f_packet_count == 1 ){
               
                handler.failed( contact, e );
              }
            }
          },
          store_timeout,
          priority );

      }
    }catch( Throwable e ){
                   
      stats.storeFailed();
     
      if ( packet_count <= 1 ){
               
        handler.failed( contact, e );
      }
    }
  }
 
    // QUERY STORE
 
  public void
  sendQueryStore(
    final DHTTransportUDPContactImpl  contact,
    final DHTTransportReplyHandler     handler,
    int                  header_size,
    List<Object[]>            key_details )
  {
    try{
      checkAddress( contact );
     
      final long  connection_id = getConnectionID();
     
      Iterator<Object[]> it = key_details.iterator();
     
      byte[]        current_prefix      = null;
      Iterator<byte[]>  current_suffixes     = null;
           
      List<DHTUDPPacketRequestQueryStorage> requests = new ArrayList<DHTUDPPacketRequestQueryStorage>();
 
outer: 
      while( it.hasNext()){

        int  space = DHTUDPPacketRequestQueryStorage.SPACE;

        DHTUDPPacketRequestQueryStorage  request =
          new DHTUDPPacketRequestQueryStorage( this, connection_id, local_contact, contact );

        List<Object[]> packet_key_details = new ArrayList<Object[]>();
       
        while( space > 0 && it.hasNext()){
         
          if ( current_prefix == null ){
         
            Object[] entry = it.next();
         
            current_prefix = (byte[])entry[0];
         
            List<byte[]> l = (List<byte[]>)entry[1];
           
            current_suffixes = l.iterator();
          }
         
          if ( current_suffixes.hasNext()){
           
            int  min_space = header_size + 3// 1 byte prefix len, 2 byte num suffix
           
            if ( space < min_space ){
             
              request.setDetails( header_size, packet_key_details );
             
              requests.add( request );
             
              continue outer ;
            }
           
            List<byte[]> s = new ArrayList<byte[]>();
           
            packet_key_details.add( new Object[]{ current_prefix, s });
           
            int  prefix_size = current_prefix.length;
            int  suffix_size = header_size - prefix_size;

            space -= ( 3 + prefix_size );
           
            while( space >= suffix_size && current_suffixes.hasNext()){
             
              s.add( current_suffixes.next());
            }
           
          }else{
           
            current_prefix = null;
          }
        }
       
        if ( !it.hasNext()){
         
          request.setDetails( header_size, packet_key_details );
         
          requests.add( request );
        }
      }

      final Object[] replies = new Object[ requests.size() ];
             
      for ( int i=0;i<requests.size();i++){
       
        DHTUDPPacketRequestQueryStorage request = requests.get(i);
       
        final int f_i = i;
         
        stats.queryStoreSent( request );
             
        requestSendRequestProcessor( contact, request );
 
        packet_handler.sendAndReceive(
          request,
          contact.getTransportAddress(),
          new DHTUDPPacketReceiver()
          {         
            public void
            packetReceived(
              DHTUDPPacketReply  packet,
              InetSocketAddress  from_address,
              long        elapsed_time )
            {
              try{                           
                if ( packet.getConnectionId() != connection_id ){
                 
                  throw( new Exception( "connection id mismatch" ));
                }
 
                contact.setInstanceIDAndVersion( packet.getTargetInstanceID(), packet.getProtocolVersion());
               
                requestSendReplyProcessor( contact, handler, packet, elapsed_time );
                 
                DHTUDPPacketReplyQueryStorage  reply = (DHTUDPPacketReplyQueryStorage)packet;
               
                  // copy out the random id in preparation for a possible subsequent
                  // store operation
               
                contact.setRandomID( reply.getRandomID());
                                             
                stats.queryStoreOK();
                 
                synchronized( replies ){
                 
                  replies[f_i] = reply;
                 
                  checkComplete();
                }
               
              }catch( DHTUDPPacketHandlerException e ){
               
                error( e );
               
              }catch( Throwable e ){
               
                Debug.printStackTrace(e);
               
                error( new DHTUDPPacketHandlerException( "queryStore failed", e ));
              }
            }
           
            public void
            error(
              DHTUDPPacketHandlerException  e )
            {
              stats.queryStoreFailed();
             
              synchronized( replies ){
               
                replies[f_i] = e;
               
                checkComplete();
              }
            }
           
            protected void
            checkComplete()
            {
              DHTUDPPacketHandlerException last_error = null;
             
              for ( int i=0;i<replies.length;i++ ){
             
                Object o = replies[i];
               
                if ( o == null ){
                 
                  return;
                }
               
                if ( o instanceof DHTUDPPacketHandlerException ){
                 
                  last_error = (DHTUDPPacketHandlerException)o;
                }
              }
             
              if ( last_error != null ){
             
                handler.failed( contact, last_error );
               
              }else{
             
                if ( replies.length == 1 ){
                 
                  handler.queryStoreReply( contact, ((DHTUDPPacketReplyQueryStorage)replies[0]).getResponse());
                 
                }else{
                 
                  List<byte[]> response = new ArrayList<byte[]>();
             
                  for ( int i=0;i<replies.length;i++ ){
                 
                    response.addAll(((DHTUDPPacketReplyQueryStorage)replies[0]).getResponse());
                  }
               
                  handler.queryStoreReply( contact, response );
                }
              }
            }
          },
          request_timeout, PRUDPPacketHandler.PRIORITY_MEDIUM );
      }
     
    }catch( Throwable e ){
     
      stats.queryStoreFailed();
     
      handler.failed( contact, e );
    }
  }
 
    // FIND NODE
 
  public void
  sendFindNode(
    final DHTTransportUDPContactImpl  contact,
    final DHTTransportReplyHandler    handler,
    byte[]                nid )
  {
    try{
      checkAddress( contact );
     
      final long  connection_id = getConnectionID();
     
      final DHTUDPPacketRequestFindNode  request =
        new DHTUDPPacketRequestFindNode( this, connection_id, local_contact, contact );
     
      stats.findNodeSent( request );
     
      request.setID( nid );
     
      request.setNodeStatus( getNodeStatus());
     
      request.setEstimatedDHTSize( request_handler.getTransportEstimatedDHTSize());
     
      requestSendRequestProcessor( contact, request );

      packet_handler.sendAndReceive(
        request,
        contact.getTransportAddress(),
        new DHTUDPPacketReceiver()
        {         
          public void
          packetReceived(
            DHTUDPPacketReply  packet,
            InetSocketAddress  from_address,
            long        elapsed_time )
          {
            try{                           
              if ( packet.getConnectionId() != connection_id ){
               
                throw( new Exception( "connection id mismatch" ));
              }

              contact.setInstanceIDAndVersion( packet.getTargetInstanceID(), packet.getProtocolVersion());
             
              requestSendReplyProcessor( contact, handler, packet, elapsed_time );
               
              DHTUDPPacketReplyFindNode  reply = (DHTUDPPacketReplyFindNode)packet;
             
                // copy out the random id in preparation for a possible subsequent
                // store operation
             
              contact.setRandomID( reply.getRandomID());
                           
              updateContactStatus( contact, reply.getNodeStatus(), false );
             
              request_handler.setTransportEstimatedDHTSize( reply.getEstimatedDHTSize());
             
              stats.findNodeOK();
               
              DHTTransportContact[] contacts = reply.getContacts();
             
                // scavenge any contacts here to help bootstrap process
                // when ip wrong and no import history
             
              for (int i=0; contact_history.size() < CONTACT_HISTORY_MAX && i<contacts.length;i++){
               
                DHTTransportUDPContact c = (DHTTransportUDPContact)contacts[i];
               
                try{
                  this_mon.enter();
                 
                  contact_history.put( c.getTransportAddress(), c );

                }finally{
                 
                  this_mon.exit();
                }
              }
             
              handler.findNodeReply( contact, contacts );
             
            }catch( DHTUDPPacketHandlerException e ){
             
              error( e );
             
            }catch( Throwable e ){
             
              Debug.printStackTrace(e);
             
              error( new DHTUDPPacketHandlerException( "findNode failed", e ));
            }
          }
         
          public void
          error(
            DHTUDPPacketHandlerException  e )
          {
            stats.findNodeFailed();
           
            handler.failed( contact, e );
          }
        },
        request_timeout, PRUDPPacketHandler.PRIORITY_MEDIUM );
     
    }catch( Throwable e ){
     
      stats.findNodeFailed();
     
      handler.failed( contact, e );
    }
  }
 
    // FIND VALUE
 
  public void
  sendFindValue(
    final DHTTransportUDPContactImpl  contact,
    final DHTTransportReplyHandler    handler,
    byte[]                key,
    int                  max_values,
    byte                flags )
  {
    try{
      checkAddress( contact );
     
      final long  connection_id = getConnectionID();
     
      final DHTUDPPacketRequestFindValue  request =
        new DHTUDPPacketRequestFindValue( this, connection_id, local_contact, contact );
     
      stats.findValueSent( request );

      request.setID( key );
     
      request.setMaximumValues( max_values );
     
      request.setFlags( flags );
     
      requestSendRequestProcessor( contact, request );

      packet_handler.sendAndReceive(
        request,
        contact.getTransportAddress(),
        new DHTUDPPacketReceiver()
        {
          public void
          packetReceived(
            DHTUDPPacketReply  packet,
            InetSocketAddress  from_address,
            long        elapsed_time )
          {
            try{             
              if ( packet.getConnectionId() != connection_id ){
               
                throw( new Exception( "connection id mismatch" ));
              }
             
              contact.setInstanceIDAndVersion( packet.getTargetInstanceID(), packet.getProtocolVersion());
             
              requestSendReplyProcessor( contact, handler, packet, elapsed_time );
               
              DHTUDPPacketReplyFindValue  reply = (DHTUDPPacketReplyFindValue)packet;
               
              stats.findValueOK();
               
              DHTTransportValue[]  res = reply.getValues();
               
              if ( res != null ){
                 
                boolean  continuation = reply.hasContinuation();
                               
                handler.findValueReply( contact, res, reply.getDiversificationType(), continuation);
                 
              }else{
                 
                handler.findValueReply( contact, reply.getContacts());
              }
            }catch( DHTUDPPacketHandlerException e ){
             
              error( e );
             
            }catch( Throwable e ){
             
              Debug.printStackTrace(e);
             
              error( new DHTUDPPacketHandlerException( "findValue failed", e ));
            }
          }
         
          public void
          error(
            DHTUDPPacketHandlerException  e )
          {
            stats.findValueFailed();
           
            handler.failed( contact, e );
          }
        },
        request_timeout, PRUDPPacketHandler.PRIORITY_HIGH );
     
    }catch( Throwable e ){
     
      if ( !(e instanceof DHTUDPPacketHandlerException )){
       
        stats.findValueFailed();
     
        handler.failed( contact, e );
      }
    }
  }
 
  protected DHTTransportFullStats
  getFullStats(
    DHTTransportUDPContactImpl  contact )
  {
    if ( contact == local_contact ){
     
      return( request_handler.statsRequest( contact ));
    }
   
    final DHTTransportFullStats[] res = { null };
   
    final AESemaphore  sem = new AESemaphore( "DHTTransportUDP:getFullStats");
   
    sendStatscontact,
          new DHTTransportReplyHandlerAdapter()
          {
            public void
            statsReply(
              DHTTransportContact   _contact,
              DHTTransportFullStats  _stats )
            {
              res[0= _stats;
             
              sem.release();
            }
           
            public void
            failed(
              DHTTransportContact   _contact,
              Throwable        _error )
            {
              sem.release();
            }
       
          });
   
    sem.reserve();

    return( res[0] );
  }
 
    // read request
 
  protected void
  sendReadRequest(
    long            connection_id, 
    DHTTransportUDPContactImpl  contact,
    byte[]            transfer_key,
    byte[]            key )
  {
    sendReadRequest( connection_id, contact, transfer_key, key, 0, 0 );
  }
 
  protected void
  sendReadRequest(
    long            connection_id, 
    DHTTransportUDPContactImpl  contact,
    byte[]            transfer_key,
    byte[]            key,
    int              start_pos,
    int              len )
  {
    final DHTUDPPacketData  request =
      new DHTUDPPacketData( this, connection_id, local_contact, contact );
     
    request.setDetails( DHTUDPPacketData.PT_READ_REQUEST, transfer_key, key, new byte[0], start_pos, len, 0 );
       
    try{
      checkAddress( contact );
     
      if ( XFER_TRACE ){
        logger.log( "Transfer read request: key = " + DHTLog.getFullString( key ) + ", contact = " + contact.getString());
      }
     
      stats.dataSent( request );
     
      packet_handler.send(
        request,
        contact.getTransportAddress());
     
    }catch( Throwable e ){
     
    }
  }
 
  protected void
  sendReadReply(
    long            connection_id, 
    DHTTransportUDPContactImpl  contact,
    byte[]            transfer_key,
    byte[]            key,
    byte[]            data,
    int              start_position,
    int              length,
    int              total_length )
  {
    final DHTUDPPacketData  request =
      new DHTUDPPacketData( this, connection_id, local_contact, contact );
     
    request.setDetails( DHTUDPPacketData.PT_READ_REPLY, transfer_key, key, data, start_position, length, total_length );
   
    try{
      checkAddress( contact );
     
      if ( XFER_TRACE ){
        logger.log( "Transfer read reply: key = " + DHTLog.getFullString( key ) + ", contact = " + contact.getString());
      }
     
      stats.dataSent( request );
     
      packet_handler.send(
        request,
        contact.getTransportAddress());
     
    }catch( Throwable e ){
     
    }
  }
 
  protected void
  sendWriteRequest(
    long            connection_id, 
    DHTTransportUDPContactImpl  contact,
    byte[]            transfer_key,
    byte[]            key,
    byte[]            data,
    int              start_position,
    int              length,
    int              total_length )
  {
    final DHTUDPPacketData  request =
      new DHTUDPPacketData( this, connection_id, local_contact, contact );
     
    request.setDetails( DHTUDPPacketData.PT_WRITE_REQUEST, transfer_key, key, data, start_position, length, total_length );
   
    try{
      checkAddress( contact );
     
      if ( XFER_TRACE ){
        logger.log( "Transfer write request: key = " + DHTLog.getFullString( key ) + ", contact = " + contact.getString());
      }
     
      stats.dataSent( request );
     
      packet_handler.send(
        request,
        contact.getTransportAddress());
     
    }catch( Throwable e ){
     
    }
  }
 
  protected void
  sendWriteReply(
    long            connection_id, 
    DHTTransportUDPContactImpl  contact,
    byte[]            transfer_key,
    byte[]            key,
    int              start_position,
    int              length )
  {
    final DHTUDPPacketData  request =
      new DHTUDPPacketData( this, connection_id, local_contact, contact );
     
    request.setDetails( DHTUDPPacketData.PT_WRITE_REPLY, transfer_key, key, new byte[0], start_position, length, 0 );
   
    try{
      checkAddress( contact );
     
      if ( XFER_TRACE ){
        logger.log( "Transfer write reply: key = " + DHTLog.getFullString( key ) + ", contact = " + contact.getString());
      }
     
      stats.dataSent( request );
     
      packet_handler.send(
        request,
        contact.getTransportAddress());
     
    }catch( Throwable e ){
     
    }
  }
 
  public void
  registerTransferHandler(
    byte[]            handler_key,
    DHTTransportTransferHandler  handler )
  {
    logger.log( "Transfer handler (" + handler.getName() + ") registered for key '" + ByteFormatter.encodeString( handler_key ));
   
    transfer_handlers.put(
      new HashWrapper( handler_key ),
      new transferHandlerInterceptor(
          handler ));
  }
 
  protected int
  handleTransferRequest(
    DHTTransportUDPContactImpl  target,
    long            connection_id,
    byte[]            transfer_key,
    byte[]            request_key,
    byte[]            data,
    int              start,
    int              length,
    boolean            write_request,
    boolean            first_packet_only )
 
    throws DHTTransportException
  {
    DHTTransportTransferHandler  handler = (DHTTransportTransferHandler)transfer_handlers.get(new HashWrapper( transfer_key ));
   
    if ( handler == null ){
     
      logger.log( "No transfer handler registered for key '" + ByteFormatter.encodeString(transfer_key) + "'" );
     
      throw( new DHTTransportException( "No transfer handler registered for " + ByteFormatter.encodeString(transfer_key) ));
    }

    if ( data == null ){
     
      data = handler.handleRead( target, request_key );
    }
   
    if ( data == null ){
     
      return( -1 );
     
    }else{
       
        // special case 0 length data
     
      if ( data.length == 0 ){
       
        if ( write_request ){
         
          sendWriteRequest(
              connection_id,
              target,
              transfer_key,
              request_key,
              data,
              0,
              0,
              0 );           
        }else{
         
          sendReadReply(
              connection_id,
              target,
              transfer_key,
              request_key,
              data,
              0,
              0,
              0 );
        }
      }else{
               
        if ( start < 0 ){
         
          start  = 0;
         
        }else if ( start >= data.length ){
         
          logger.log( "dataRequest: invalid start position" );
         
          return( data.length );
        }
               
        if ( length <= 0 ){
         
          length = data.length;
         
        }else if ( start + length > data.length ){
         
          logger.log( "dataRequest: invalid length" );
         
          return( data.length );
        }
       
        int  end = start+length;
       
        while( start < end ){
         
          int  chunk = end - start;
         
          if ( chunk > DHTUDPPacketData.MAX_DATA_SIZE ){
           
            chunk = DHTUDPPacketData.MAX_DATA_SIZE;               
          }
         
          if ( write_request ){
           
            sendWriteRequest(
                connection_id,
                target,
                transfer_key,
                request_key,
                data,
                start,
                chunk,
                data.length );
                       
            if ( first_packet_only ){
             
              break;
            }
          }else{
           
            sendReadReply(
                connection_id,
                target,
                transfer_key,
                request_key,
                data,
                start,
                chunk,
                data.length );
          }
         
          start += chunk;
        }
      }
     
      return( data.length );
    }
  }
 
  protected void
  dataRequest(
    final DHTTransportUDPContactImpl  originator,
    final DHTUDPPacketData        req )
  {
    /*
    if ((int)(Math.random() * 4 )== 0 ){
     
      System.out.println("dropping request packet:" + req.getString());
     
      return;
    }
    */
   
    stats.dataReceived();
   
      // both requests and replies come through here. Currently we only support read
      // requests so we can safely use the data.length == 0 test to discriminate between
      // a request and a reply to an existing transfer
   
    byte  packet_type = req.getPacketType();
   
    if ( XFER_TRACE ){
      System.out.println( "dataRequest: originator=" + originator.getAddress() + ",packet=" + req.getString());
    }
   
    if ( packet_type == DHTUDPPacketData.PT_READ_REPLY ){
     
      transferQueue  queue = lookupTransferQueue( read_transfers, req.getConnectionId());
     
        // unmatched -> drop it
     
      if ( queue != null ){
     
        queue.add( req );
      }
     
    }else if ( packet_type == DHTUDPPacketData.PT_WRITE_REPLY ){
       
      transferQueue  queue = lookupTransferQueue( write_transfers, req.getConnectionId());
       
        // unmatched -> drop it
       
      if ( queue != null ){
       
        queue.add( req );
      }
    }else{
     
      byte[]  transfer_key = req.getTransferKey();
                 
      if ( packet_type == DHTUDPPacketData.PT_READ_REQUEST ){

        try{
          handleTransferRequest(
              originator,
              req.getConnectionId(),
              transfer_key,
              req.getRequestKey(),
              null,
              req.getStartPosition(),
              req.getLength(),
              false, false );
         
        }catch( DHTTransportException e ){
         
          logger.log(e);
        }

      }else{
       
          //   write request
         
        transferQueue  old_queue = lookupTransferQueue( read_transfers, req.getConnectionId());
       
        if ( old_queue != null ){
         
          old_queue.add( req );
         
        }else{
       
          final DHTTransportTransferHandler  handler = (DHTTransportTransferHandler)transfer_handlers.get(new HashWrapper( transfer_key ));
         
          if ( handler == null ){
           
            logger.log( "No transfer handler registered for key '" + ByteFormatter.encodeString(transfer_key) + "'" );
           
          }else{
                     
            try{
              final transferQueue new_queue = new transferQueue( read_transfers, req.getConnectionId());
           
                // add the initial data for this write request
             
              new_queue.add( req );
             
                // set up the queue processor
               
              try{
                this_mon.enter();
               
                if ( active_write_queue_processor_count >= TRANSFER_QUEUE_MAX ){
                 
                  new_queue.destroy();
                 
                  throw( new DHTTransportException( "Active write queue process thread limit exceeded" ));
                }
               
                active_write_queue_processor_count++;
               
                if ( XFER_TRACE ){
                  System.out.println( "active_write_queue_processor_count=" + active_write_queue_processor_count );
                }
              }finally{
               
                this_mon.exit();
              }

              new AEThread2( "DHTTransportUDP:writeQueueProcessor", true )
                {
                  public void
                  run()
                  {
                    try{
                      byte[] write_data =
                        runTransferQueue(
                          new_queue,
                          new DHTTransportProgressListener()
                          {
                            public void
                            reportSize(
                              long  size )
                            {
                              if ( XFER_TRACE ){
                                System.out.println( "writeXfer: size=" + size );
                              }
                            }
                           
                            public void
                            reportActivity(
                              String  str )
                            {
                              if ( XFER_TRACE ){
                                System.out.println( "writeXfer: act=" + str );
                              }
                            }
                           
                            public void
                            reportCompleteness(
                              int    percent )
                            {
                              if ( XFER_TRACE ){
                                System.out.println( "writeXfer: %=" + percent );
                              }
                            }
                          },
                          originator,
                          req.getTransferKey(),
                          req.getRequestKey(),
                          60000,
                          false );
                     
                      if ( write_data != null ){
                       
                          // xfer complete, send ack if multi-packet xfer
                          // (ack already sent below if single packet)
                       
                        if (   req.getStartPosition() != 0 ||
                            req.getLength() != req.getTotalLength() ){
                         
                          sendWriteReply(
                              req.getConnectionId(),
                              originator,
                              req.getTransferKey(),
                              req.getRequestKey(),
                              0,
                              req.getTotalLength());
                        }
                       
                        byte[]  reply_data = handler.handleWrite( originator, req.getRequestKey(), write_data );
                       
                        if ( reply_data != null ){
                         
                          writeTransfer(
                              new DHTTransportProgressListener()
                              {
                                public void
                                reportSize(
                                  long  size )
                                {
                                  if ( XFER_TRACE ){
                                    System.out.println( "writeXferReply: size=" + size );
                                  }
                                }
                               
                                public void
                                reportActivity(
                                  String  str )
                                {
                                  if ( XFER_TRACE ){
                                    System.out.println( "writeXferReply: act=" + str );
                                  }
                                }
                               
                                public void
                                reportCompleteness(
                                  int    percent )
                                {
                                  if ( XFER_TRACE ){
                                    System.out.println( "writeXferReply: %=" + percent );
                                  }
                                }
                              },
                              originator,
                              req.getTransferKey(),
                              req.getRequestKey(),
                              reply_data,
                              WRITE_REPLY_TIMEOUT );
                             
                        }
                      }
                     
                    }catch( DHTTransportException e ){
                     
                      logger.log( "Failed to process transfer queue: " + Debug.getNestedExceptionMessage(e));
                     
                    }finally{
                     
                      try{
                        this_mon.enter();
                                               
                        active_write_queue_processor_count--;
                       
                        if ( XFER_TRACE ){
                          System.out.println( "active_write_queue_processor_count=" + active_write_queue_processor_count );
                        }
                      }finally{
                       
                        this_mon.exit();
                      }
                    }
                  }
                }.start();
               
                  // indicate that at least one packet has been received
               
              sendWriteReply(
                req.getConnectionId(),
                originator,
                req.getTransferKey(),
                req.getRequestKey(),
                req.getStartPosition(),
                req.getLength());
             
            }catch( DHTTransportException e ){
             
              logger.log( "Faild to create transfer queue" );
             
              logger.log( e );
            }
          }
        }
      }
    }
  }
   
  public byte[]
  readTransfer(
    DHTTransportProgressListener  listener,
    DHTTransportContact        target,
    byte[]              handler_key,
    byte[]              key,
    long              timeout )
 
    throws DHTTransportException
  {
    long  connection_id   = getConnectionID();
   
    transferQueue  transfer_queue = new transferQueue( read_transfers, connection_id );
   
    return( runTransferQueue( transfer_queue, listener, target, handler_key, key, timeout, true ));
  }
 
  protected byte[]
  runTransferQueue(
    transferQueue          transfer_queue,
    DHTTransportProgressListener  listener,
    DHTTransportContact        target,
    byte[]              handler_key,
    byte[]              key,
    long              timeout,
    boolean              read_transfer )
 
    throws DHTTransportException
  {   
    SortedSet  packets =
      new TreeSet(
        new Comparator()
        {
          public int
          compare(
            Object  o1,
            Object  o2 )
          {
            DHTUDPPacketData  p1 = (DHTUDPPacketData)o1;
            DHTUDPPacketData  p2 = (DHTUDPPacketData)o2;
           
            return( p1.getStartPosition() - p2.getStartPosition());
          }
        });
   
    int  entire_request_count = 0;
   
    int transfer_size   = -1;
    int  transferred    = 0;
   
    String  target_name = DHTLog.getString2(target.getID());
   
    try{
      long  start = SystemTime.getCurrentTime();
     
      if ( read_transfer ){
     
        listener.reportActivity( getMessageText( "request_all", target_name ));

        entire_request_count++;
     
        sendReadRequest( transfer_queue.getID(), (DHTTransportUDPContactImpl)target, handler_key, key );

      }else{
       
          // write transfer - data already on its way, no need to request it
       
        entire_request_count++;
      }
     
      while( SystemTime.getCurrentTime() - start <= timeout ){         
       
        DHTUDPPacketData  reply = transfer_queue.receive( READ_XFER_REREQUEST_DELAY );
       
        if ( reply != null ){
 
          if ( transfer_size == -1 ){
           
            transfer_size = reply.getTotalLength();
           
            listener.reportSize( transfer_size );
          }
         
          Iterator  it = packets.iterator();
         
          boolean  duplicate = false;
         
          while( it.hasNext()){
           
            DHTUDPPacketData  p = (DHTUDPPacketData)it.next();
           
              // ignore overlaps
           
            if p.getStartPosition() < reply.getStartPosition() + reply.getLength() &&
                p.getStartPosition() + p.getLength() > reply.getStartPosition()){
             
              duplicate  = true;
             
              break;
            }
          }
         
          if ( !duplicate ){
           
            listener.reportActivity(
                getMessageText( "received_bit",
                new String[]{
                    String.valueOf( reply.getStartPosition()),
                    String.valueOf(reply.getStartPosition() + reply.getLength()),
                    target_name }));

            transferred += reply.getLength();
           
            listener.reportCompleteness( transfer_size==0?100: ( 100 * transferred / transfer_size ));
           
            packets.add( reply );
           
              // see if we're done       
         
            it = packets.iterator();
           
            int  pos      = 0;
            int  actual_end  = -1;
           
            while( it.hasNext()){
             
              DHTUDPPacketData  p = (DHTUDPPacketData)it.next();
           
              if ( actual_end == -1 ){
               
                actual_end = p.getTotalLength();
              }
             
              if ( p.getStartPosition() != pos ){
               
                  // missing data, give up
               
                break;
              }
             
              pos += p.getLength();
             
              if ( pos == actual_end ){
             
                  // huzzah, we got the lot
             
                listener.reportActivity( getMessageText( "complete" ));
               
                byte[]  result = new byte[actual_end];
               
                it =  packets.iterator();
               
                pos  = 0;
               
                while( it.hasNext()){
                 
                  p = (DHTUDPPacketData)it.next();

                  System.arraycopy( p.getData(), 0, result, pos, p.getLength());
                 
                  pos  += p.getLength();
                }
               
                return( result );
              }
            }
          }
        }else{
         
            // timeout, look for missing bits
         
          if ( packets.size() == 0 ){
           
            if ( entire_request_count == 2 ){
           
              listener.reportActivity( getMessageText( "timeout", target_name ));
             
              return( null );
            }
           
            entire_request_count++;
           
            listener.reportActivity( getMessageText( "rerequest_all", target_name ));
           
            sendReadRequest( transfer_queue.getID(), (DHTTransportUDPContactImpl)target, handler_key, key );
           
          }else{
           
            Iterator it = packets.iterator();
         
            int  pos      = 0;
            int  actual_end  = -1;
           
            while( it.hasNext()){
             
              DHTUDPPacketData  p = (DHTUDPPacketData)it.next();
           
              if ( actual_end == -1 ){
               
                actual_end = p.getTotalLength();
              }
             
              if ( p.getStartPosition() != pos ){
               
                listener.reportActivity(
                    getMessageText( "rerequest_bit",
                        new String[]{
                          String.valueOf( pos ),
                          String.valueOf( p.getStartPosition()),
                          target_name }));
               
                sendReadRequest(
                    transfer_queue.getID(),
                    (DHTTransportUDPContactImpl)target,
                    handler_key,
                    key,
                    pos,
                    p.getStartPosition()-pos );
             
              }
             
              pos = p.getStartPosition() + p.getLength();
            }
           
            if ( pos != actual_end ){
             
              listener.reportActivity(
                  getMessageText( "rerequest_bit",
                      new String[]{
                        String.valueOf( pos ),
                        String.valueOf( actual_end ),
                        target_name }));
     
              sendReadRequest(
                  transfer_queue.getID(),
                  (DHTTransportUDPContactImpl)target,
                  handler_key,
                  key,
                  pos,
                  actual_end - pos );           
            }
          }
        }
      }
     
      if ( packets.size()==0 ){
       
        listener.reportActivity( getMessageText( "timeout", target_name ));
       
      }else{
       
        listener.reportActivity(
            getMessageText(
              "timeout_some",
              new String[]{ String.valueOf( packets.size()), target_name }));
             
      }
     
      return( null );
     
    }finally{
     
      transfer_queue.destroy();
    }
  }
 
  public void
  writeTransfer(
    DHTTransportProgressListener  listener,
    DHTTransportContact        target,
    byte[]              handler_key,
    byte[]              key,
    byte[]              data,
    long              timeout )
 
    throws DHTTransportException
  {
    transferQueue  transfer_queue = null;
 
    try{
      long  connection_id   = getConnectionID();
   
      transfer_queue = new transferQueue( write_transfers, connection_id );
   
      boolean  ok         = false;
      boolean  reply_received  = false;
     
      int    loop      = 0;
      int    total_length  = data.length;
     
      long  start = SystemTime.getCurrentTime();
     
      long  last_packet_time = 0;
     
      while( true ){
       
        long  now = SystemTime.getCurrentTime();
       
        if ( now < start ){
         
          start        = now;
         
          last_packet_time  = 0;
         
        }else{
         
          if ( now - start > timeout ){
           
            break;
          }
        }
       
        long  time_since_last_packet = now - last_packet_time;
       
        if ( time_since_last_packet >= WRITE_XFER_RESEND_DELAY ){
         
          listener.reportActivity( getMessageText( loop==0?"sending":"resending" ));
       
          loop++;
       
          total_length =  handleTransferRequest(
                        (DHTTransportUDPContactImpl)target,
                        connection_id,
                        handler_key,
                        key,
                        data,
                        -1, -1,
                        true,
                        reply_received )// first packet only if we've has a reply
       
          last_packet_time    = now;
          time_since_last_packet  = 0;
        }
       
        DHTUDPPacketData packet = transfer_queue.receive( WRITE_XFER_RESEND_DELAY - time_since_last_packet );
     
        if ( packet != null ){
         
          last_packet_time  = now;
         
          reply_received = true;
         
          if ( packet.getStartPosition() == 0 && packet.getLength() == total_length ){
           
            ok  = true;
         
            break;
          }
        }
      }
     
      if ( ok ){
       
        listener.reportCompleteness( 100 );
       
        listener.reportActivity( getMessageText( "send_complete" ));
       
      }else{
       
        listener.reportActivity( getMessageText( "send_timeout" ));
       
        throw( new DHTTransportException( "Timeout" ));
      }
    }finally{
     
      if ( transfer_queue != null ){
       
        transfer_queue.destroy();
      }
    }
  }
 
  public byte[]
  writeReadTransfer(
    DHTTransportProgressListener  listener,
    DHTTransportContact        target,
    byte[]              handler_key,
    byte[]              data,
    long              timeout
 
    throws DHTTransportException
  {
    byte[]  call_key = new byte[20];
   
    random.nextBytes( call_key );
   
    AESemaphore  call_sem = new AESemaphore( "DHTTransportUDP:calSem" );
   
    HashWrapper  wrapped_key = new HashWrapper( call_key );
   
    try{
      this_mon.enter();
     
      call_transfers.put( wrapped_key, call_sem );
   
    }finally{
     
      this_mon.exit();
    }
   
    writeTransfer( listener, target, handler_key, call_key, data, timeout );
   
    if ( call_sem.reserve( timeout )){
     
      try{
        this_mon.enter();
     
        Object  res = call_transfers.remove( wrapped_key );
       
        if ( res instanceof byte[] ){
         
          return((byte[])res);
        }
      }finally{
       
        this_mon.exit();
      }
    }
   
    throw( new DHTTransportException( "timeout" ));
  }
 
 
  public void
  process(
    DHTUDPPacketRequest  request,
    boolean        alien )
  {
    if ( request_handler == null ){
     
      logger.log( "Ignoring packet as not yet ready to process" );
     
      return;
    }
   
    try{     
      stats.incomingRequestReceived( request, alien );
     
      InetSocketAddress  transport_address = request.getAddress();
     
      DHTTransportUDPContactImpl  originating_contact =
        new DHTTransportUDPContactImpl(
            false,
            this,
            transport_address,
            request.getOriginatorAddress(),
            request.getOriginatorVersion(),
            request.getOriginatorInstanceID(),
            request.getClockSkew());
     
      try{
        checkAddress( originating_contact );
         
      }catch( DHTUDPPacketHandlerException e ){
       
        return;
      }

      requestReceiveRequestProcessor( originating_contact, request );
     
      boolean  bad_originator = !originating_contact.addressMatchesID();
     
        // bootstrap node returns details regardless of whether the originator ID matches
        // as the details will help the sender discover their correct ID (hopefully)
     
      if ( bad_originator && !bootstrap_node ){
       
        String  contact_string = originating_contact.getString();

        if ( recent_reports.get(contact_string) == null ){
         
          recent_reports.put( contact_string, "" );
         
          logger.log( "Node " + contact_string + " has incorrect ID, reporting it to them" );
        }
       
        DHTUDPPacketReplyError  reply =
          new DHTUDPPacketReplyError(
              this,
              request.getTransactionId(),
              request.getConnectionId(),
              local_contact,
              originating_contact );
       
        reply.setErrorType( DHTUDPPacketReplyError.ET_ORIGINATOR_ADDRESS_WRONG );
       
        reply.setOriginatingAddress( originating_contact.getTransportAddress());
       
        requestReceiveReplyProcessor( originating_contact, reply );
       
        packet_handler.send( reply, request.getAddress());

      }else{
       
        if ( bad_originator ){
       
            // we need to patch the originator up otherwise we'll be populating our
            // routing table with crap
         
          originating_contact =
            new DHTTransportUDPContactImpl(
                false,
                this,
                transport_address,
                transport_address,     // set originator address to transport
                request.getOriginatorVersion(),
                request.getOriginatorInstanceID(),
                request.getClockSkew());

        }else{
         
          contactAlive( originating_contact );
        }
       
        if ( request instanceof DHTUDPPacketRequestPing ){
         
          if ( !bootstrap_node ){
           
            request_handler.pingRequest( originating_contact );
           
            DHTUDPPacketReplyPing  reply =
              new DHTUDPPacketReplyPing(
                  this,
                  request.getTransactionId(),
                  request.getConnectionId(),
                  local_contact,
                  originating_contact );
           
            requestReceiveReplyProcessor( originating_contact, reply );

            packet_handler.send( reply, request.getAddress());
          }
        }else if ( request instanceof DHTUDPPacketRequestKeyBlock ){
           
          if ( !bootstrap_node ){
           
            DHTUDPPacketRequestKeyBlock  kb_request = (DHTUDPPacketRequestKeyBlock)request;

            originating_contact.setRandomID( kb_request.getRandomID());

            request_handler.keyBlockRequest(
                originating_contact,
                kb_request.getKeyBlockRequest(),
                kb_request.getKeyBlockSignature());
           
            DHTUDPPacketReplyKeyBlock  reply =
              new DHTUDPPacketReplyKeyBlock(
                  this,
                  request.getTransactionId(),
                  request.getConnectionId(),
                  local_contact,
                  originating_contact );
           
            requestReceiveReplyProcessor( originating_contact, reply );

            packet_handler.send( reply, request.getAddress());
          }
        }else if ( request instanceof DHTUDPPacketRequestStats ){
         
          DHTUDPPacketRequestStats  stats_request = (DHTUDPPacketRequestStats)request;
                   
          DHTUDPPacketReplyStats  reply =
            new DHTUDPPacketReplyStats(
                this,
                request.getTransactionId(),
                request.getConnectionId(),
                local_contact,
                originating_contact );
         
          int  type = stats_request.getStatsType();
         
          if ( type == DHTUDPPacketRequestStats.STATS_TYPE_ORIGINAL ){
           
            DHTTransportFullStats  full_stats = request_handler.statsRequest( originating_contact );

            reply.setOriginalStats( full_stats );
           
          }else if ( type == DHTUDPPacketRequestStats.STATS_TYPE_NP_VER2 ){
           
            DHTNetworkPositionProvider prov = DHTNetworkPositionManager.getProvider( DHTNetworkPosition.POSITION_TYPE_VIVALDI_V2 );
           
            byte[]  data = new byte[0];
           
            if ( prov != null ){
             
              ByteArrayOutputStream  baos = new ByteArrayOutputStream();
             
              DataOutputStream  dos = new DataOutputStream( baos );
             
              prov.serialiseStats( dos );
             
              dos.flush();
             
              data = baos.toByteArray();
            }
           
            reply.setNewStats( data, DHTNetworkPosition.POSITION_TYPE_VIVALDI_V2 );
           
          }else{
           
            throw( new IOException( "Uknown stats type '" + type + "'" ));
          }
         
          requestReceiveReplyProcessor( originating_contact, reply );

          packet_handler.send( reply, request.getAddress());

        }else if ( request instanceof DHTUDPPacketRequestStore ){
         
          if ( !bootstrap_node ){

            DHTUDPPacketRequestStore  store_request = (DHTUDPPacketRequestStore)request;
           
            originating_contact.setRandomID( store_request.getRandomID());
           
            DHTTransportStoreReply  res =
              request_handler.storeRequest(
                originating_contact,
                store_request.getKeys(),
                store_request.getValueSets());
           
            if ( res.blocked()){
             
              if ( originating_contact.getProtocolVersion() >= DHTTransportUDP.PROTOCOL_VERSION_BLOCK_KEYS ){
               
                DHTUDPPacketReplyError  reply =
                  new DHTUDPPacketReplyError(
                    this,
                    request.getTransactionId(),
                    request.getConnectionId(),
                    local_contact,
                    originating_contact );
               
                reply.setErrorType( DHTUDPPacketReplyError.ET_KEY_BLOCKED );
               
                reply.setKeyBlockDetails( res.getBlockRequest(), res.getBlockSignature() );
               
                requestReceiveReplyProcessor( originating_contact, reply );

                packet_handler.send( reply, request.getAddress());
              }else{
               
                DHTUDPPacketReplyStore  reply =
                  new DHTUDPPacketReplyStore(
                      this,
                      request.getTransactionId(),
                      request.getConnectionId(),
                      local_contact,
                      originating_contact );
               
                reply.setDiversificationTypes( new byte[store_request.getKeys().length] );
               
                requestReceiveReplyProcessor( originating_contact, reply );
   
                packet_handler.send( reply, request.getAddress());
              }
            }else{
             
              DHTUDPPacketReplyStore  reply =
                new DHTUDPPacketReplyStore(
                    this,
                    request.getTransactionId(),
                    request.getConnectionId(),
                    local_contact,
                    originating_contact );
             
              reply.setDiversificationTypes( res.getDiversificationTypes());
             
              requestReceiveReplyProcessor( originating_contact, reply );
 
              packet_handler.send( reply, request.getAddress());
            }
          }
        }else if ( request instanceof DHTUDPPacketRequestQueryStorage ){
         
          DHTUDPPacketRequestQueryStorage  query_request = (DHTUDPPacketRequestQueryStorage)request;
         
          DHTTransportQueryStoreReply  res =
            request_handler.queryStoreRequest(
                  originating_contact,
                  query_request.getHeaderLength(),
                  query_request.getKeys());   
         
          DHTUDPPacketReplyQueryStorage  reply =
            new DHTUDPPacketReplyQueryStorage(
                this,
                request.getTransactionId(),
                request.getConnectionId(),
                local_contact,
                originating_contact );
             
          reply.setRandomID( originating_contact.getRandomID());
           
          reply.setResponse( res.getHeaderSize(), res.getEntries());
         
          requestReceiveReplyProcessor( originating_contact, reply );

          packet_handler.send( reply, request.getAddress());
         
        }else if ( request instanceof DHTUDPPacketRequestFindNode ){
         
          DHTUDPPacketRequestFindNode  find_request = (DHTUDPPacketRequestFindNode)request;
         
          boolean  acceptable;
         
            // as a bootstrap node we only accept find-node requests for the originator's
            // ID
         
          if ( bootstrap_node ){
           
              // log( originating_contact );
           
              // let bad originators through to aid bootstrapping with bad IP
           
            acceptable = bad_originator || Arrays.equals( find_request.getID(), originating_contact.getID());
                       
          }else{
           
            acceptable  = true;
          }
         
          if ( acceptable ){
           
            if ( find_request.getProtocolVersion() >= DHTTransportUDP.PROTOCOL_VERSION_MORE_NODE_STATUS ){
           
              updateContactStatus( originating_contact, find_request.getNodeStatus(), true );
                     
              request_handler.setTransportEstimatedDHTSize( find_request.getEstimatedDHTSize());
            }
           
            DHTTransportContact[]  res =
              request_handler.findNodeRequest(
                    originating_contact,
                    find_request.getID());
           
            DHTUDPPacketReplyFindNode  reply =
              new DHTUDPPacketReplyFindNode(
                  this,
                  request.getTransactionId(),
                  request.getConnectionId(),
                  local_contact,
                  originating_contact );
               
            reply.setRandomID( originating_contact.getRandomID());
           
            reply.setNodeStatus( getNodeStatus());
           
            reply.setEstimatedDHTSize( request_handler.getTransportEstimatedDHTSize());
           
            reply.setContacts( res );
           
            requestReceiveReplyProcessor( originating_contact, reply );

            packet_handler.send( reply, request.getAddress());
          }
         
        }else if ( request instanceof DHTUDPPacketRequestFindValue ){
         
          if ( !bootstrap_node ){

            DHTUDPPacketRequestFindValue  find_request = (DHTUDPPacketRequestFindValue)request;
         
            DHTTransportFindValueReply res =
              request_handler.findValueRequest(
                    originating_contact,
                    find_request.getID(),
                    find_request.getMaximumValues(),
                    find_request.getFlags());
         
            if ( res.blocked()){
             
              if ( originating_contact.getProtocolVersion() >= DHTTransportUDP.PROTOCOL_VERSION_BLOCK_KEYS ){
               
                DHTUDPPacketReplyError  reply =
                  new DHTUDPPacketReplyError(
                    this,
                    request.getTransactionId(),
                    request.getConnectionId(),
                    local_contact,
                    originating_contact );
               
                reply.setErrorType( DHTUDPPacketReplyError.ET_KEY_BLOCKED );
               
                reply.setKeyBlockDetails( res.getBlockedKey(), res.getBlockedSignature() );
               
                requestReceiveReplyProcessor( originating_contact, reply );

                packet_handler.send( reply, request.getAddress());
               
              }else{
               
                DHTUDPPacketReplyFindValue  reply =
                  new DHTUDPPacketReplyFindValue(
                    this,
                    request.getTransactionId(),
                    request.getConnectionId(),
                    local_contact,
                    originating_contact );
               
                reply.setValues( new DHTTransportValue[0], DHT.DT_NONE, false );
               
                requestReceiveReplyProcessor( originating_contact, reply );
 
                packet_handler.send( reply, request.getAddress());
              }
             
            }else{
              DHTUDPPacketReplyFindValue  reply =
                new DHTUDPPacketReplyFindValue(
                  this,
                  request.getTransactionId(),
                  request.getConnectionId(),
                  local_contact,
                  originating_contact );
             
              if ( res.hit()){
               
                DHTTransportValue[]  res_values = res.getValues();
               
                int    max_size = DHTUDPPacketHelper.PACKET_MAX_BYTES - DHTUDPPacketReplyFindValue.DHT_FIND_VALUE_HEADER_SIZE;
                             
                List  values     = new ArrayList();
                int    values_size  = 0;
               
                int  pos = 0;
               
                while( pos < res_values.length ){
             
                  DHTTransportValue  v = res_values[pos];
                 
                  int  v_len = v.getValue().length + DHTUDPPacketReplyFindValue.DHT_FIND_VALUE_TV_HEADER_SIZE;
                 
                  if (   values_size > 0 && // if value too big, cram it in anyway
                      values_size + v_len > max_size ){
                   
                      // won't fit, send what we've got
                   
                    DHTTransportValue[]  x = new DHTTransportValue[values.size()];
                   
                    values.toArray( x );
                                     
                    reply.setValues( x, res.getDiversificationType(), true )// continuation = true
                                   
                    packet_handler.send( reply, request.getAddress());
                   
                    values_size  = 0;
                   
                    values    = new ArrayList();
                   
                  }else{
                   
                    values.add(v);
                   
                    values_size  += v_len;
                   
                    pos++;
                  }
                }
               
                  // send the remaining (possible zero length) non-continuation values
                 
                DHTTransportValue[]  x = new DHTTransportValue[values.size()];
                 
                values.toArray( x );
                 
                reply.setValues( x, res.getDiversificationType(), false );
                 
                requestReceiveReplyProcessor( originating_contact, reply );
 
                packet_handler.send( reply, request.getAddress());
             
              }else{
               
                reply.setContacts(res.getContacts());
               
                requestReceiveReplyProcessor( originating_contact, reply );
 
                packet_handler.send( reply, request.getAddress());
              }
            }
          }
        }else if ( request instanceof DHTUDPPacketData ){
         
          if ( !bootstrap_node ){
           
            dataRequest(originating_contact, (DHTUDPPacketData)request );
          }
        }else{
         
          Debug.out( "Unexpected packet:" + request.toString());
        }
      }
    }catch( DHTUDPPacketHandlerException e ){
     
      // not interesting, send packet fail or something
     
    }catch( Throwable e ){
     
      Debug.printStackTrace(e);
    }
  }
 
  protected void
  requestReceiveRequestProcessor(
    DHTTransportUDPContactImpl  contact,
    DHTUDPPacketRequest      request )
  {
    // called when request received
  }

  protected void
  requestReceiveReplyProcessor(
    DHTTransportUDPContactImpl  contact,
    DHTUDPPacketReply      reply )
  {
    // called before sending reply to request
   
    int  action = reply.getAction();
   
    if (   action == DHTUDPPacketHelper.ACT_REPLY_PING ||
        action == DHTUDPPacketHelper.ACT_REPLY_FIND_NODE ||
        action == DHTUDPPacketHelper.ACT_REPLY_FIND_VALUE ){
           
      reply.setNetworkPositions( local_contact.getNetworkPositions());
    }
  }
 
  protected void
  requestSendRequestProcessor(
    DHTTransportUDPContactImpl  contact,
    DHTUDPPacketRequest     request )
  {
    // called before sending request
   
  }
 
    /**
     * Returns false if this isn't an error reply, true if it is and a retry can be
     * performed, throws an exception otherwise
     * @param reply
     * @return
     */
 
  protected void
  requestSendReplyProcessor(
    DHTTransportUDPContactImpl  remote_contact,
    DHTTransportReplyHandler  handler,
    DHTUDPPacketReply      reply,
    long            elapsed_time )
 
    throws DHTUDPPacketHandlerException
  {
      // called after receiving reply to request
   
    // System.out.println( "request:" + contact.getAddress() + " = " + elapsed_time );
   
    DHTNetworkPosition[]  remote_nps = reply.getNetworkPositions();
   
    if ( remote_nps != null ){
     
        // save current position of target
     
      remote_contact.setNetworkPositions( remote_nps );

        // update local positions
     
      DHTNetworkPositionManager.update( local_contact.getNetworkPositions(), remote_contact.getID(), remote_nps, (float)elapsed_time );           
    }
   
    if ( reply.getAction() == DHTUDPPacketHelper.ACT_REPLY_ERROR ){
     
      DHTUDPPacketReplyError  error = (DHTUDPPacketReplyError)reply;
           
      switch( error.getErrorType()){
     
        case DHTUDPPacketReplyError.ET_ORIGINATOR_ADDRESS_WRONG:
        {
          try{
            externalAddressChange( remote_contact, error.getOriginatingAddress(), false );
           
          }catch( DHTTransportException e ){
           
            Debug.printStackTrace(e);
          }
                     
          throw( new DHTUDPPacketHandlerException( "address changed notification" ));
        }
        case DHTUDPPacketReplyError.ET_KEY_BLOCKED:
        {
          handler.keyBlockRequest( remote_contact, error.getKeyBlockRequest(), error.getKeyBlockSignature());
         
          contactAlive( remote_contact );
         
          throw( new DHTUDPPacketHandlerException( "key blocked" ));
        }
      }
       
      throw( new DHTUDPPacketHandlerException( "unknown error type " + error.getErrorType()));
     
    }else{
     
      contactAlive( remote_contact );
    }
  }
 
  protected long
  getConnectionID()
  {
      // unfortunately, to reuse the UDP port with the tracker protocol we
      // have to distinguish our connection ids by setting the MSB. This allows
      // the decode to work as there is no common header format for the request
      // and reply packets
   
      // note that tracker usage of UDP via this handler is only for outbound
      // messages, hence for that use a request will never be received by the
      // handler
   
    return( 0x8000000000000000L | random.nextLong());
  }
 
  public boolean
  supportsStorage()
  {
    return( !bootstrap_node );
  }
 
  public void
  addListener(
    DHTTransportListener  l )
  {
    listeners.add(l);
   
    if ( external_address != null ){
     
      l.currentAddress( external_address );
    }
  }
 
  public void
  removeListener(
    DHTTransportListener  l )
  {
    listeners.remove(l);
  }

  protected transferQueue
  lookupTransferQueue(
    Map      transfers,
    long    id )
  {
    try{
      this_mon.enter();

      return((transferQueue)transfers.get(new Long(id)));
     
    }finally{
     
      this_mon.exit();
   
  }
 
  protected String
  getMessageText(
    String  resource )
  {
    return( MessageText.getString( "DHTTransport.report." + resource ));
  }
 
  protected String
  getMessageText(
    String  resource,
    String  param )
  {
    return( MessageText.getString( "DHTTransport.report." + resource, new String[]{ param }));
  }
 
  protected String
  getMessageText(
    String    resource,
    String[]  params )
  {
    return( MessageText.getString( "DHTTransport.report." + resource, params));
  }
 
  protected class
  transferQueue
  {
    private Map      transfers;
    private long    id;
    private boolean    destroyed;
   
    private List    packets  = new ArrayList();
   
    private AESemaphore  packets_sem  = new AESemaphore("DHTUDPTransport:transferQueue");
   
    protected
    transferQueue(
      Map      _transfers,
      long    _id )
   
      throws DHTTransportException
    {
      transfers  = _transfers;
      id      = _id;
     
      try{
        this_mon.enter();

        if ( transfers.size() > TRANSFER_QUEUE_MAX ){
         
          Debug.out( "Transfer queue count limit exceeded" );
         
          throw( new DHTTransportException( "Transfer queue limit exceeded" ));
        }
       
        Long l_id = new Long( id );
       
        transferQueue  existing = (transferQueue)transfers.get( l_id );
       
        if ( existing != null ){
         
          existing.destroy();
        }
       
        transfers.put( l_id, this );
       
      }finally{
       
        this_mon.exit();
      }     
    }
   
    protected long
    getID()
    {
      return( id );
    }
   
    protected void
    add(
      DHTUDPPacketData  packet )
    {
      try{
        this_mon.enter();
 
        if ( destroyed ){
         
          return;
        }
       
        if ( total_bytes_on_transfer_queues > MAX_TRANSFER_QUEUE_BYTES ){
         
          Debug.out( "Transfer queue byte limit exceeded" );
       
            // just drop the packet
         
          return;
        }
       
        int  length = packet.getLength();
       
        total_bytes_on_transfer_queues += length;
       
        if ( XFER_TRACE ){
          System.out.println( "total_bytes_on_transfer_queues=" + total_bytes_on_transfer_queues );
        }
       
        packets.add( packet );
       
      }finally{
       
        this_mon.exit();
      }
     
      packets_sem.release();
    }
   
    protected DHTUDPPacketData
    receive(
      long  timeout )
    {
      if ( packets_sem.reserve( timeout )){
       
        try{
          this_mon.enter();
           
          if ( destroyed ){
           
            return( null );
          }
         
          DHTUDPPacketData packet = (DHTUDPPacketData)packets.remove(0);
         
          int  length = packet.getLength();
         
          total_bytes_on_transfer_queues -= length;

          if ( XFER_TRACE ){
            System.out.println( "total_bytes_on_transfer_queues=" + total_bytes_on_transfer_queues );
          }
         
          return( packet );
         
        }finally{
         
          this_mon.exit();
        }       
      }else{
       
        return( null );
      }
    }
   
    protected void
    destroy()
    {
      try{
        this_mon.enter();
         
        destroyed = true;
       
        transfers.remove( new Long( id ));
       
        for (int i=0;i<packets.size();i++){
         
          DHTUDPPacketData  packet = (DHTUDPPacketData)packets.get(i);
         
          int  length = packet.getLength();
         
          total_bytes_on_transfer_queues -= length;
         
          if ( XFER_TRACE ){
            System.out.println( "total_bytes_on_transfer_queues=" + total_bytes_on_transfer_queues );
          }
        }
       
        packets.clear();
       
        packets_sem.releaseForever();
       
      }finally{
       
        this_mon.exit();
      }
    }
  }
 
  /*
  private PrintWriter  contact_log;
  private int      contact_log_entries;
  private SimpleDateFormat  contact_log_format = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss");
  {
    contact_log_format.setTimeZone( TimeZone.getTimeZone( "UTC" ));
  }
 
  protected void
  log(
    DHTTransportUDPContactImpl    contact )
  {
    if ( network == DHT.NW_MAIN ){
     
      synchronized( this ){
   
        try{
          if ( contact_log == null ){
           
            contact_log = new PrintWriter( new FileWriter( new File( SystemProperties.getUserPath(), "contact_log" )));
          }
         
          contact_log_entries++;
         
          InetSocketAddress address = contact.getAddress();
         
          contact_log.println( contact_log_format.format( new Date()) + ", " + address.getAddress().getHostAddress() + ", " + address.getPort());
         
          if ( contact_log_entries % 1000 == 0 ){
           
            System.out.println( "contact-log: " + contact_log_entries );
           
            contact_log.flush();
          }
         
        }catch( Throwable e ){
         
          Debug.out( e );
        }
      }
    }
  }
  */
 
  protected class
  transferHandlerInterceptor
    implements DHTTransportTransferHandler
  {
    private DHTTransportTransferHandler    handler;
   
    protected
    transferHandlerInterceptor(
      DHTTransportTransferHandler    _handler )
    {
      handler  = _handler;
    }
   
    public String
    getName()
    {
      return( handler.getName());
    }
   
    public byte[]
      handleRead(
        DHTTransportContact  originator,
        byte[]        key )
    {
      return( handler.handleRead( originator, key ));
    }
     
      public byte[]
      handleWrite(
        DHTTransportContact  originator,
        byte[]        key,
        byte[]        value )
      {
        HashWrapper  key_wrapper = new HashWrapper( key );
       
          // see if this is the response to an outstanding call
       
        try{
          this_mon.enter();
         
          Object  obj = call_transfers.get( key_wrapper );
         
          if ( obj instanceof AESemaphore ){
           
            AESemaphore  sem = (AESemaphore)obj;
           
            call_transfers.put( key_wrapper, value );
           
            sem.release();
           
            return( null );
          }
        }finally{
         
          this_mon.exit();
        }
       
        return( handler.handleWrite( originator, key, value ));
      }
  }
}
TOP

Related Classes of com.aelitis.azureus.core.dht.transport.udp.impl.DHTTransportUDPImpl

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.