Package org.gudy.azureus2.core3.tracker.client.impl.bt

Source Code of org.gudy.azureus2.core3.tracker.client.impl.bt.TRTrackerBTAnnouncerImpl

/*
* File    : TRTrackerBTAnnouncerImpl.java
* Created : 5 Oct. 2003
* By      : Parg
*
* Azureus - a Java Bittorrent client
*
* 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.
*
* 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 ( see the LICENSE file ).
*
* 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
*/

package org.gudy.azureus2.core3.tracker.client.impl.bt;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.util.*;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.logging.LogAlert;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.peer.PEPeerSource;
import org.gudy.azureus2.core3.security.SESecurityManager;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentAnnounceURLSet;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.tracker.client.*;
import org.gudy.azureus2.core3.tracker.client.impl.*;
import org.gudy.azureus2.core3.tracker.protocol.PRHelpers;
import org.gudy.azureus2.core3.tracker.protocol.udp.*;
import org.gudy.azureus2.core3.tracker.util.TRTrackerUtils;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.core3.util.Timer;
import org.gudy.azureus2.plugins.clientid.ClientIDException;
import org.gudy.azureus2.plugins.clientid.ClientIDGenerator;
import org.gudy.azureus2.plugins.download.DownloadAnnounceResult;
import org.gudy.azureus2.plugins.download.DownloadAnnounceResultPeer;
import org.gudy.azureus2.pluginsimpl.local.clientid.ClientIDManagerImpl;

import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPosition;
import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPositionManager;
import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin;
import com.aelitis.azureus.core.networkmanager.impl.udp.UDPNetworkManager;
import com.aelitis.azureus.core.peermanager.utils.PeerClassifier;
import com.aelitis.azureus.core.tracker.TrackerPeerSource;
import com.aelitis.net.udp.uc.*;


/**
*
* This class handles communication with the tracker
*
* @author Olivier
*
*/
public class
TRTrackerBTAnnouncerImpl
  implements TRTrackerAnnouncerHelper
{
  public final static LogIDs LOGID = LogIDs.TRACKER;
 
  private static final int OVERRIDE_PERIOD      = 10*1000;
  
  protected static Timer  tracker_timer = new Timer( "Tracker Timer", 32);
 
  public static String   UDP_REALM = "UDP Tracker";
 
  private static int userMinInterval = 0;
  private static int userMaxNumwant = 100;
  private static boolean udpAnnounceEnabled;
  private static boolean udpProbeEnabled;

    static{
      PRUDPTrackerCodecs.registerCodecs();
      COConfigurationManager.addAndFireParameterListeners(
        new String[] {
          "Tracker Client Min Announce Interval",
          "Tracker Client Numwant Limit",
          "Server Enable UDP",
          "Tracker UDP Probe Enable"
          },
        new ParameterListener()
        {
          public void parameterChanged(final String parameterName)
          {
            userMinInterval = COConfigurationManager.getIntParameter("Tracker Client Min Announce Interval");
            userMaxNumwant = COConfigurationManager.getIntParameter("Tracker Client Numwant Limit");
            udpAnnounceEnabled = COConfigurationManager.getBooleanParameter("Server Enable UDP");
          udpProbeEnabled  = COConfigurationManager.getBooleanParameter("Tracker UDP Probe Enable");
          }       
        });
    }
 
  private static AEMonitor   class_mon       = new AEMonitor( "TRTrackerBTAnnouncer:class" );
  private static Map      tracker_report_map  = new HashMap();
 
   
  private TOTorrent          torrent;
  private TOTorrentAnnounceURLSet[]  announce_urls;
 
  private TRTrackerAnnouncerImpl.Helper  helper;
 
  private TimerEvent        current_timer_event;
  private TimerEventPerformer    timer_event_action;
 
  protected int        tracker_state       = TRTrackerAnnouncer.TS_INITIALISED;
  private String        tracker_status_str    = "";
  private TRTrackerAnnouncerResponseImpl  last_response      = null;
  private long        last_update_time_secs;
  private long        current_time_to_wait_secs;
  private boolean        manual_control;
 
  private long    tracker_interval;
  private long    tracker_min_interval;
 
 
  private long     min_interval = 0;
 
  private int  failure_added_time = 0;
    private long failure_time_last_updated = 0;
 
  private boolean      stopped;
  private boolean      stopped_for_queue;
  private boolean      completed;
  private boolean      complete_reported  = false;
 
  private boolean      update_in_progress  = false;
 
  private long      rd_last_override = 0;
  private int        rd_override_percentage  = 100;

  private long      min_interval_override  = 0;
 
    private List trackerUrlLists;
    
    private URL lastUsedUrl;
    private URL  lastAZTrackerCheckedURL;
   
    private HashWrapper      torrent_hash;
   
  private String  last_tracker_message;    // per torrent memory
 
  private String info_hash = "info_hash=";
  private byte[] tracker_peer_id;
  private String tracker_peer_id_str = "&peer_id=";
 
  private byte[] data_peer_id;
   
 
  private int announceCount;
  private int announceFailCount;
 
  private byte autoUDPprobeEvery = 1;
 
 
  private String tracker_id = "";
 
 
  private String     ip_override;
  private String[]  peer_networks;
   
  private TRTrackerAnnouncerDataProvider   announce_data_provider;
 
  protected AEMonitor this_mon   = new AEMonitor( "TRTrackerBTAnnouncer" );

  private boolean  az_tracker;
  private boolean  destroyed;
   

 
  public
  TRTrackerBTAnnouncerImpl(
     TOTorrent            _torrent,
     TOTorrentAnnounceURLSet[]    _announce_urls,
  String[]            _peer_networks,
  boolean              _manual,
  TRTrackerAnnouncerImpl.Helper  _helper )
   
    throws TRTrackerAnnouncerException
  {
  torrent      = _torrent;
  announce_urls  = _announce_urls;
    peer_networks  = _peer_networks;
    manual_control  = _manual;
    helper      = _helper;
   
    //Get the Tracker url
   
  constructTrackerUrlLists( true );
      
    //Create our unique peerId
 
  try{
    data_peer_id = helper.getPeerID();
 
      if ( COConfigurationManager.getBooleanParameter("Tracker Separate Peer IDs")){
          
        tracker_peer_id = ClientIDManagerImpl.getSingleton().generatePeerID( torrent, true );
       
      }else{
       
        tracker_peer_id  = data_peer_id;
      }
  }catch( ClientIDException e ){

     throw( new TRTrackerAnnouncerException( "TRTrackerAnnouncer: Peer ID generation fails", e ));
  }
 
  try {
 
    torrent_hash = _torrent.getHashWrapper();
       
    this.info_hash += URLEncoder.encode(new String(torrent_hash.getBytes(), Constants.BYTE_ENCODING), Constants.BYTE_ENCODING).replaceAll("\\+", "%20");
   
    this.tracker_peer_id_str += URLEncoder.encode(new String(tracker_peer_id, Constants.BYTE_ENCODING), Constants.BYTE_ENCODING).replaceAll("\\+", "%20");
   
  }catch (UnsupportedEncodingException e){

    Logger.log(new LogEvent(torrent, LOGID, "URL encode fails", e));
   
    throw( new TRTrackerAnnouncerException( "TRTrackerAnnouncer: URL encode fails"));
   
  }catch( TOTorrentException e ){
 
    Logger.log(new LogEvent(torrent, LOGID, "Torrent hash retrieval fails", e));
   
    throw( new TRTrackerAnnouncerException( "TRTrackerAnnouncer: URL encode fails"))
  }
    
  timer_event_action = 
    new TimerEventPerformer()
    {
      public void
      perform(
        TimerEvent  this_event )
      {
        if ( manual_control ){
         
          requestUpdateSupport();
         
          return;
        }
       
        long  secs_to_wait = getErrorRetryInterval();
             
        try{
                             
          secs_to_wait = requestUpdateSupport();

          if (Logger.isEnabled())
            Logger.log(new LogEvent(torrent, LOGID,
                "Next tracker announce (unadjusted) will be in " + secs_to_wait
                    + "s"));
               
        }finally{
           
          current_time_to_wait_secs  = secs_to_wait;
             
          if ( tracker_state == TRTrackerAnnouncer.TS_STOPPED ){
           
            // System.out.println( "\tperform: stopped so no more events");
           
          }else{
           
         
            try{
              this_mon.enter();
           
                // it is possible that the current event was being processed
                // when another thread cancelled it and created a further timer
                // event. if this is the case we don't want to go ahead and
                // create another timer as one already exists
               
              if ( this_event.isCancelled()){
               
                // System.out.println( "\tperform: cancelled so no new event");
               
              }else{
               
               
                secs_to_wait = getAdjustedSecsToWait();

                if (Logger.isEnabled())
                  Logger.log(new LogEvent(torrent, LOGID,
                      "Next tracker announce (adjusted) will be in "
                          + secs_to_wait + "s"));
               
                long target_time = SystemTime.getCurrentTime() + (secs_to_wait*1000);
               
                if ( current_timer_event != null && !current_timer_event.isCancelled()){
                 
                  if (   current_timer_event != this_event &&
                      current_timer_event.getWhen() < target_time ){
                 
                      // existing event is earlier then current target, use it
                       
                    return;
                  }
                 
                  current_timer_event.cancel();
                }
               
                if ( !destroyed ){
                 
                  current_timer_event =
                    tracker_timer.addEvent( target_time, this );
                }
              }
            }finally{
             
              this_mon.exit();
            }
          }
        }
      }
    };
   
    if (Logger.isEnabled())
      Logger.log(new LogEvent(torrent, LOGID,
          "Tracker Announcer Created using url : " + trackerURLListToString()));
  }
   
  public void
  cloneFrom(
    TRTrackerBTAnnouncerImpl  other )
  {
    helper          = other.helper;
    data_peer_id      = other.data_peer_id;
    tracker_peer_id      = other.tracker_peer_id;
    tracker_peer_id_str    = other.tracker_peer_id_str;
    tracker_id        = other.tracker_id;

    announce_data_provider  = other.announce_data_provider;
  }
 
  protected long
  getAdjustedSecsToWait()
  {

    long    secs_to_wait = current_time_to_wait_secs;
                         
    if( last_response != null && last_response.getStatus() != TRTrackerAnnouncerResponse.ST_ONLINE ) {
     
      if( last_response.getStatus() == TRTrackerAnnouncerResponse.ST_REPORTED_ERROR ) {
       
        //the tracker has explicitly reported an error (torrent is unauthorized for example),
        //so there's no need to keep trying to re-announce as if it were actually offline

        //there's no "min interval" returned, so start the re-announce backoff timings at 15min
        if( failure_added_time < 900 failure_added_time = 900;
        secs_to_wait = getErrorRetryInterval();
       
        if (Logger.isEnabled()) {
          Logger.log(new LogEvent(torrent, LOGID,
              "MIN INTERVAL CALC: tracker reported error, " +
              "adjusting to error retry interval"));
        }
      }
      else //tracker is OFFLINE
        secs_to_wait = getErrorRetryInterval();
        if (Logger.isEnabled()) {
          Logger.log(new LogEvent(torrent, LOGID,
              "MIN INTERVAL CALC: tracker seems to be offline, " +
              "adjusting to error retry interval"));
        }
      }
             
    }
    else{
       
      if( rd_override_percentage == 0 ) {
        if (Logger.isEnabled())
          Logger.log(new LogEvent(torrent, LOGID,
              "MIN INTERVAL CALC: override, perc = 0"));
        return TRTrackerAnnouncer.REFRESH_MINIMUM_SECS;
      }

      if (rd_override_percentage != 100) {
        secs_to_wait = (secs_to_wait * rd_override_percentage) /100;
        if (Logger.isEnabled()) {
          Logger.log(new LogEvent(torrent, LOGID,
              "MIN INTERVAL CALC: override, perc = " + rd_override_percentage));
        }
      }
                 
      if ( secs_to_wait < TRTrackerAnnouncer.REFRESH_MINIMUM_SECS ){
         
        secs_to_wait = TRTrackerAnnouncer.REFRESH_MINIMUM_SECS;
      }
     
      //use 'min interval' for calculation
      if( min_interval != 0 && secs_to_wait < min_interval ) {
        float percentage = (float)min_interval / current_time_to_wait_secs;  //percentage of original interval
       
        //long orig_override = secs_to_wait;
       
        int added_secs = (int)((min_interval - secs_to_wait) * percentage)//increase by x percentage of difference
        secs_to_wait += added_secs;
       
        if (Logger.isEnabled())
          Logger.log(new LogEvent(torrent, LOGID,
              "MIN INTERVAL CALC: min_interval=" + min_interval + ", interval="
                  + current_time_to_wait_secs + ", orig=" +  current_time_to_wait_secs
                  + ", new=" + secs_to_wait + ", added=" + added_secs
                  + ", perc=" + percentage));
      }
     
    }
     
    return( secs_to_wait );
  }
 
 
  public int
    getStatus()
    {
      return( tracker_state );
    }
 
    public String
    getStatusString()
    {
      return( tracker_status_str );
    }
   
  public TRTrackerAnnouncer
  getBestAnnouncer()
  {
    return( this );
  }
 
  public void
  setRefreshDelayOverrides(
    int    percentage )
  {
    if ( percentage > 100 ){
     
      percentage = 100;
     
    }else if ( percentage < 50 ){
     
      percentage  = 50;
    }
   
    long  now = SystemTime.getCurrentTime();
    //only start overriding once the tracker announce update has been called
    boolean override_allowed = rd_last_override > 0 && now - rd_last_override > OVERRIDE_PERIOD;
   
    if( now < rd_last_override override_allowed = true//time went backwards
   
    if ( override_allowed && rd_override_percentage != percentage ){
   
      try{
        this_mon.enter();

        rd_last_override  = now;
       
        rd_override_percentage  = percentage;
       
        if ( current_timer_event != null && !current_timer_event.isCancelled()){
         
          long  start   = current_timer_event.getCreatedTime();
          long  expiry  = current_timer_event.getWhen();
         
          long  secs_to_wait = getAdjustedSecsToWait();
               
          long target_time = start + (secs_to_wait*1000);

          if ( target_time != expiry ){
           
            current_timer_event.cancel();
           
            if ( !destroyed ){
             
              if (Logger.isEnabled())
                Logger.log(new LogEvent(torrent, LOGID,
                    "Changed next tracker announce to " + secs_to_wait
                        + "s via " + Debug.getStackTrace(true, false, 0, 3)));
             
              current_timer_event =
                tracker_timer.addEvent(
                  start,
                  target_time,
                  timer_event_action )
            }
          }     
        }
      }finally{
       
        this_mon.exit();
      }
    }
  }
 
  public boolean
  isUpdating()
  {
    return( update_in_progress );
  }
 
  public long
  getInterval()
  {
    return( tracker_interval );
  }
 
  public long
  getMinInterval()
  {
    return( tracker_min_interval );
  }
 
  public int
  getTimeUntilNextUpdate()
  {
    try{
      this_mon.enter();
   
      if ( current_timer_event == null ){
       
        return( getErrorRetryInterval() );
      }
         
      int rem = (int)((current_timer_event.getWhen() - SystemTime.getCurrentTime())/1000);
         
      return( rem );
     
    }finally{
     
      this_mon.exit();
    }
  }

  public int
  getLastUpdateTime()
  {
    return( (int)last_update_time_secs );
  }

  public void
  update(
    boolean    force )
  {
    long now = SystemTime.getCurrentTime() / 1000;
       
        if ( now < last_update_time_secs force = true//time went backwards

        long  effective_min = min_interval_override>0?min_interval_override:TRTrackerAnnouncer.REFRESH_MINIMUM_SECS;
       
    if ( manual_control || force || ( now - last_update_time_secs >= effective_min )){
     
      requestUpdate();
    }
  }
 
   
  public void
  complete(
    boolean  already_reported )
  {
    complete_reported  = (complete_reported || already_reported );
   
    completed      = true;
   
    requestUpdate();
  }
 
  public void
  stop(
    boolean  for_queue )
  {
    stopped        = true;
        stopped_for_queue  = for_queue;
       
    requestUpdate();
  }
 
  protected void
  requestUpdate()
  {
    try{
      this_mon.enter();
     
      if ( current_timer_event != null ){
       
        current_timer_event.cancel();
      }
     
      rd_last_override = SystemTime.getCurrentTime()//"pause" overrides for 10s
     
      if ( !destroyed ){
       
        if (Logger.isEnabled())
          Logger.log(new LogEvent(torrent, LOGID,
              "Forcing tracker announce now via "
                  + Debug.getStackTrace(true, false, 0, 3)));

        current_timer_event =
          tracker_timer.addEvent(
            SystemTime.getCurrentTime(),
            timer_event_action );
      }
    }finally{
     
      this_mon.exit();
    }
  }
 
  protected long
  requestUpdateSupport()
  {
   
    boolean  clear_progress = true;
   
    try{
      try{
        this_mon.enter();

          // can't continue if the data provider hasn't been set yet...
       
        if ( update_in_progress || announce_data_provider == null ){
         
          clear_progress = false;
         
          return( getErrorRetryInterval() );
        }
       
        update_in_progress = true;
       
      }finally{
       
        this_mon.exit();
      }
 
      last_update_time_secs  = SystemTime.getCurrentTime()/1000;
     
      tracker_status_str = MessageText.getString("PeerManager.status.checking") + "..."; //$NON-NLS-1$ //$NON-NLS-2$     
   
      TRTrackerAnnouncerResponseImpl  response = null;
     
      if ( stopped ){
       
          // if manual control then we assume that a stop request is required, even if we
          // are in an init state. needed for explicit stop based on URL
       
        if ( tracker_state == TRTrackerAnnouncer.TS_INITIALISED && !manual_control ){
         
            // never started
         
          tracker_state = TRTrackerAnnouncer.TS_STOPPED;
         
        }else if ( tracker_state != TRTrackerAnnouncer.TS_STOPPED ){
     
          response = stopSupport();
         
          if ( response.getStatus() == TRTrackerAnnouncerResponse.ST_ONLINE ){
                       
            tracker_state = TRTrackerAnnouncer.TS_STOPPED;
           
          }else{
           
              // just have one go at sending a stop event as we don't want to sit here
              // forever trying to send stop to a stuffed tracker
             
            tracker_state = TRTrackerAnnouncer.TS_STOPPED;
          }
        } 
      }else if ( tracker_state == TRTrackerAnnouncer.TS_INITIALISED ){
             
          // always go through the "start" phase, even if we're already complete
          // as some trackers insist on the initial "start"
       
        response = startSupport();
         
        if ( response.getStatus() == TRTrackerAnnouncerResponse.ST_ONLINE ){
           
          tracker_state = TRTrackerAnnouncer.TS_DOWNLOADING;
        }
      }else if ( completed ){
       
        if ( !complete_reported ){
         
          response = completeSupport();
         
            // treat the "complete" as processed if the tracker replies either OK or an explicit
            // error. In particular, if the tracker has returned an error to control seed limits
            // we don't want to treat this as an error and report completed again next time around
            // as this causes the tracker to double count stats
         
          if ( response.getStatus() != TRTrackerAnnouncerResponse.ST_OFFLINE ){
           
            complete_reported  = true;
       
            tracker_state = TRTrackerAnnouncer.TS_COMPLETED;
          }
        }else{
          tracker_state = TRTrackerAnnouncer.TS_COMPLETED;
         
          response = updateSupport();
        }
       
      }else{
       
        response = updateSupport();
      }
           
      if ( response != null ){

        int  rs = response.getStatus();
       
        if ( rs == TRTrackerAnnouncerResponse.ST_OFFLINE ){
     
          tracker_status_str = MessageText.getString("PeerManager.status.offline");
               
        }else if ( rs == TRTrackerAnnouncerResponse.ST_REPORTED_ERROR ){

          tracker_status_str = MessageText.getString("PeerManager.status.error");
                 
            // move state back to initialised to next time around a "started"
            // event it resent. Required for trackers like 123torrents.com that
            // will fail peers that don't start with a "started" event after a
            // tracker restart
         
          tracker_state  = TRTrackerAnnouncer.TS_INITIALISED;
         
        }else{
                    
          if ( announce_data_provider.isPeerSourceEnabled( PEPeerSource.PS_BT_TRACKER )){
           
            tracker_status_str = MessageText.getString("PeerManager.status.ok");

          }else{
           
            tracker_status_str = MessageText.getString("PeerManager.status.ps_disabled");
           
            response.setPeers(new TRTrackerAnnouncerResponsePeerImpl[0]);
          }
        }

        String  reason = response.getAdditionalInfo();
       
        if ( reason != null ){
          tracker_status_str += " (" + reason + ")";   
        }
       
        last_response = response;
       
        helper.informResponse( this, response );
       
        return( response.getTimeToWait());
      }
       
      tracker_status_str = "";
     
      return( getErrorRetryInterval() );
     
    }catch( Throwable e ){
     
      Debug.printStackTrace( e );
     
      return( getErrorRetryInterval() );
     
    }finally{
     
      try{
        this_mon.enter();
     
        if ( clear_progress ){
         
          update_in_progress = false;
        }
      }finally{
       
        this_mon.exit();
      }
    }
  }
 
  protected TRTrackerAnnouncerResponseImpl startSupport() {
    if (Logger.isEnabled())
      Logger.log(new LogEvent(torrent, LOGID, "Tracker Announcer is sending "
          + "a start Request"));

    return (update("started"));
  }

  protected TRTrackerAnnouncerResponseImpl completeSupport() {
    if (Logger.isEnabled())
      Logger.log(new LogEvent(torrent, LOGID, "Tracker Announcer is sending "
          + "a completed Request"));

    return (update("completed"));
  }

  protected TRTrackerAnnouncerResponseImpl stopSupport() {
    if (Logger.isEnabled())
      Logger.log(new LogEvent(torrent, LOGID, "Tracker Announcer is sending "
          + "a stopped Request"));

    return (update("stopped"));
  }

  protected TRTrackerAnnouncerResponseImpl updateSupport() {
    if (Logger.isEnabled())
      Logger.log(new LogEvent(torrent, LOGID, "Tracker Announcer is sending "
          + "an update Request"));

    return update("");
  }
 
    private TRTrackerAnnouncerResponseImpl
  update(
    String evt )
    {
      // this method filters out any responses incompatible with the network selection
     
      TRTrackerAnnouncerResponseImpl  resp = update2( evt );
     
      TRTrackerAnnouncerResponsePeer[]  peers = resp.getPeers();
     
      if ( peers != null ){
        List  p = new ArrayList();
       
        for (int i=0;i<peers.length;i++){
         
          TRTrackerAnnouncerResponsePeer  peer = peers[i];
         
          if ( peer_networks == null ){
           
            p.add( peer );
           
          }else{
           
            String  peer_address = peer.getAddress();
           
            String  peer_network = AENetworkClassifier.categoriseAddress( peer_address );
           
            boolean  added = false;
           
            for (int j=0;j<peer_networks.length;j++){
             
              if ( peer_networks[j] == peer_network ){
               
                p.add( peer );
               
                added = true;
               
                break;
              }
            }
         
            if (!added && Logger.isEnabled())
              Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_WARNING,
                  "Tracker Announcer dropped peer '" + peer_address
                      + "' as incompatible " + "with network selection"));
          }
        }
       
        peers = new TRTrackerAnnouncerResponsePeer[ p.size()];
       
        p.toArray( peers );
       
        resp.setPeers( peers );
      }
     
      return( resp );
    }
   
  private TRTrackerAnnouncerResponseImpl
  update2(String evt)
  {
    TRTrackerAnnouncerResponseImpl  last_failure_resp = null;
 
    String  skip_host = null;
   
  outer:
   
  for (int i = 0 ; i < trackerUrlLists.size() ; i++) {
     
    List urls = (List) trackerUrlLists.get(i);
   
    for (int j = 0 ; j < urls.size() ; j++) {
     
      final URL original_url = (URL)urls.get(j);
     
      if ( skip_host != null && skip_host.equals( original_url.getHost())){
       
      if (Logger.isEnabled())
          Logger.log(
            new LogEvent(
              torrent,
              LOGID,
              LogEvent.LT_WARNING,
              "Tracker Announcer is ignoring '" + original_url + "' as already received overloaded response from this host" ));

      continue;
      }
     
      lastUsedUrl = original_url;
      
      if ( lastUsedUrl != lastAZTrackerCheckedURL ){
       
        az_tracker = TRTrackerUtils.isAZTracker( lastUsedUrl  );
      }
     
      URL  request_url = null;
     
      if ( last_failure_resp != null ){
       
          // report this now as it is about to be lost
       
        helper.informResponse( this, last_failure_resp );
      }
     
      try{
     
        request_url = constructUrl(evt,original_url);
             
        URL[]  tracker_url = { original_url };
       
        byte[]  result_bytes = updateOld( tracker_url, request_url);
       
        lastUsedUrl = tracker_url[0]// url may have redirected, use this value as it will be correct
       
      TRTrackerAnnouncerResponseImpl resp = decodeTrackerResponse( lastUsedUrl, result_bytes );
       
      int  resp_status = resp.getStatus();
     
        if ( resp_status == TRTrackerAnnouncerResponse.ST_ONLINE ){
         
          try{
              // tracker looks ok, make any redirection permanent
           
            if ( !original_url.toString().equals(lastUsedUrl.toString())){
         
              if (Logger.isEnabled())
              Logger.log(new LogEvent(torrent, LOGID,
                  "announce url permanently redirected: old = " + original_url + ", new = " + lastUsedUrl ));
           
         
            TorrentUtils.replaceAnnounceURL( torrent, original_url, lastUsedUrl );
           
          }
          }catch( Throwable e ){
           
            Debug.printStackTrace(e);
          }

              urls.remove(j);
               
              urls.add(0, lastUsedUrl )
               
              trackerUrlLists.remove(i);
               
              trackerUrlLists.add(0,urls);           
             
              informURLChange( original_url, lastUsedUrl, false );
               
                //and return the result
                 
              return( resp );
             
       }else  if ( resp_status == TRTrackerAnnouncerResponse.ST_REPORTED_ERROR ){
       
         last_failure_resp = resp; 

         String  reason = resp.getAdditionalInfo();
        
           // avoid re-hitting a host with multiple ports if reporting overloaded. This is
           // particularly "interesting" when reporting the "completed" event and we get a
           // "overloaded" response - when we hit another port we record the event twice
           // as the tracker has discarded this peer and therefore doesn't know to ignore the
           // second "completed" event...
        
         if ( reason != null &&
             (   reason.indexOf( "too many seeds" ) != -1 ||
               reason.indexOf( "too many peers" ) != -1 )){
              
           skip_host  = original_url.getHost();
         }
              
       }else{
           
         announceFailCount++;
        
         last_failure_resp = resp;
       }
      
      }catch( MalformedURLException e ){
       
      announceFailCount++;
     
        Debug.printStackTrace( e );
       
        last_failure_resp =
          new TRTrackerAnnouncerResponseImpl(
              original_url,
              torrent_hash,
              TRTrackerAnnouncerResponse.ST_OFFLINE,
            getErrorRetryInterval(),
            "malformed URL '" + (request_url==null?"<null>":request_url.toString()) + "'" );
       
      }catch( Throwable e ){
         
      announceFailCount++;

        last_failure_resp =
          new TRTrackerAnnouncerResponseImpl(
              original_url,
              torrent_hash,
              TRTrackerAnnouncerResponse.ST_OFFLINE,
            getErrorRetryInterval(),
            e.getMessage()==null?e.toString():e.getMessage());
      }
   
        if ( destroyed ){
       
        break outer;
        }
    }
    }
    
    // things no good here
 
    if ( last_failure_resp == null ){
     
        last_failure_resp =
          new TRTrackerAnnouncerResponseImpl(
              null,
              torrent_hash,
              TRTrackerAnnouncerResponse.ST_OFFLINE,
            getErrorRetryInterval(),
            "Reason Unknown" );
   
    }
    
    // use 4* the num_want as no doubt a fair few connections will fail and
    // we want to get a decent reconnect rate
 
    int  num_want = calculateNumWant() * 4;


      TRTrackerAnnouncerResponsePeer[]  cached_peers = helper.getPeersFromCache(num_want);     
     
      if ( cached_peers.length > 0 ){

        // System.out.println( "cached peers used:" + cached_peers.length );
       
        last_failure_resp.setPeers( cached_peers );
      }
     
      return( last_failure_resp );
  }

   private byte[]
   updateOld(
     URL[]    tracker_url,
     URL     reqUrl )
 
      throws Exception
  {
         // set context in case authentication dialog is required
    
     boolean errorLevel = true;
     
     try{
        TorrentUtils.setTLSTorrentHash( torrent_hash );
       
         // loop to possibly retry update on SSL certificate install
      
       for (int i=0;i<2;i++){ 
      
          String  failure_reason = null;

        String  protocol = reqUrl.getProtocol();

        try
         
          if (Logger.isEnabled()){
            Logger.log(new LogEvent(torrent, LOGID,
                "Tracker Announcer is Requesting: " + reqUrl));
          }
         
            ByteArrayOutputStream message = new ByteArrayOutputStream();
           
           
            URL udpAnnounceURL = null;
           
            boolean  udp_probe = false;
           
              // if we have multiple tracker URLs then do something sensible about
           
            if ( protocol.equalsIgnoreCase("udp")){
             
              if ( udpAnnounceEnabled ){
             
                udpAnnounceURL = reqUrl;
               
              }else{
               
                throw( new IOException( "UDP Tracker protocol disabled" ));
              }
           
          }else if protocol.equalsIgnoreCase("http") &&
                !az_tracker  &&
                announceCount % autoUDPprobeEvery == 0 &&
                udpAnnounceEnabled ){
           
              // if we don't know this tracker supports UDP then don't probe on
              // first announce as we don't want a large delay on torrent startup
              // also if we are stopping we don't want to initiate a probe as
              // we want the stop instruction to get to tracker if possible
           
            if (  (   stopped ||
                  announceCount == 0 ||
                  ( announceCount < trackerUrlLists.size() && announceFailCount == announceCount )) &&
                !TRTrackerUtils.isUDPProbeOK(reqUrl)){
             
              // skip probe
             
            }else{
              udpAnnounceURL = new URL(reqUrl.toString().replaceFirst("^http", "udp"));
             
              udp_probe = true;
            }
          }
               
            if ( udpAnnounceURL != null ){
             
            failure_reason = announceUDP( reqUrl, message, udp_probe );
           
            if ((failure_reason != null || message.size() == 0) && udp_probe){
             
                // automatic UDP probe failed, use HTTP again
             
              udpAnnounceURL = null;
             
              if ( autoUDPprobeEvery < 16 ){
           
                autoUDPprobeEvery <<= 1;
              }else{
               
                  // unregister in case the tracker somehow changed its capabilities
           
                TRTrackerUtils.setUDPProbeResult(reqUrl, false);
              }
             
              if (Logger.isEnabled()){
                Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_INFORMATION, "redirection of http announce [" + tracker_url[0] + "] to udp failed, will retry in " + autoUDPprobeEvery + " announces"));
             
            }else if (failure_reason == null && udp_probe){
             
              TRTrackerUtils.setUDPProbeResult(reqUrl, true);
             
              if (Logger.isEnabled()){
                Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_INFORMATION, "redirection of http announce [" + tracker_url[0] + "] to udp successful"));
              }
             
              autoUDPprobeEvery = 1;
            }
          }
           
            announceCount++;
           
            if ( udpAnnounceURL == null){
             
              failure_reason = announceHTTP( tracker_url, reqUrl, message );
            }
 
              // if we've got some kind of response then return it
           
          if ( message.size() > 0 ){
           
            return( message.toByteArray());
          }
           
          if ( failure_reason == null ){
           
            failure_reason = "No data received from tracker";
           
            if ( reqUrl.getProtocol().equalsIgnoreCase( "udp" )){
           
              errorLevel = false;
            }
          }
        }catch( SSLException e ){
         
          // e.printStackTrace();
                 
            // try and install certificate regardless of error (as this changed in JDK1.5
            // and broke this...)
         
          if ( i == 0 ){//&& e.getMessage().indexOf("No trusted certificate found") != -1 ){
           
            if ( SESecurityManager.installServerCertificates( reqUrl ) != null ){
             
                // certificate has been installed
             
              continue// retry with new certificate
             
            }
             
            failure_reason = exceptionToString( e );
           
          }else{
           
            failure_reason = exceptionToString( e );
           
          }
        }catch( IOException e ){
         
          if ( e instanceof UnknownHostException || e instanceof ConnectException ){
           
            errorLevel = false;
          }
         
             if ( i == 0 && protocol.toLowerCase().startsWith( "http" )){
               
                URL retry_url = UrlUtils.getIPV4Fallback( reqUrl );
               
                if ( retry_url != null ){
                 
                  reqUrl = retry_url;
                 
                  continue;
                }
             }
            
             failure_reason = exceptionToString( e );
            
        }catch (Exception e){
       
            // e.printStackTrace();
       
            failure_reason = exceptionToString( e );
        }
         
        if ( failure_reason != null && failure_reason.indexOf("401" ) != -1 ){
           
          failure_reason = "Tracker authentication failed";
          errorLevel = false;
        }
     
        if (Logger.isEnabled())
          Logger.log(new LogEvent(torrent, LOGID, errorLevel ? LogEvent.LT_ERROR : LogEvent.LT_WARNING,
              "Exception while processing the Tracker Request for " + reqUrl + ": "
                  + failure_reason));
       
        throw( new Exception( failure_reason));
       }
      
         // should never get here as second iteration of loop will always cause an exit
      
       throw( new Exception( "Internal Error: should never get here" ));
      
    }finally{
     
      TorrentUtils.setTLSTorrentHash( null );
    }
    }
 
  
   protected String
   announceHTTP(
     URL[]          tracker_url,  // overwritten if redirected
     URL            original_reqUrl,
     ByteArrayOutputStream  message )
  
     throws IOException
   {    
     TRTrackerUtils.checkForBlacklistedURLs( original_reqUrl );
    
     URL reqUrl = TRTrackerUtils.adjustURLForHosting( original_reqUrl );
    
     reqUrl = AddressUtils.adjustURL( reqUrl );
    
     if ( reqUrl != original_reqUrl ){
      if (Logger.isEnabled()){
        Logger.log(new LogEvent(torrent, LOGID,
            "    HTTP: url adjusted to " + reqUrl ));
      }
     }
    
     String  failure_reason = null;
    
     HttpURLConnection con;
    
     Properties  http_properties = new Properties();
    
     http_properties.put( ClientIDGenerator.PR_URL, reqUrl );
    
     try{
       ClientIDManagerImpl.getSingleton().generateHTTPProperties( http_properties );
      
     }catch( ClientIDException e ){
      
       throw( new IOException( e.getMessage()));
     }
   
     reqUrl = (URL)http_properties.get( ClientIDGenerator.PR_URL );
    
     if ( reqUrl.getProtocol().equalsIgnoreCase("https")){
      
       // see ConfigurationChecker for SSL client defaults
      
       HttpsURLConnection ssl_con = (HttpsURLConnection)reqUrl.openConnection();
      
       // allow for certs that contain IP addresses rather than dns names
      
       ssl_con.setHostnameVerifier(
           new HostnameVerifier()
           {
             public boolean
             verify(
                 String    host,
                SSLSession  session )
             {
               return( true );
             }
           });
      
      
       con = ssl_con;
      
     }else{
      
       con = (HttpURLConnection) reqUrl.openConnection();
     }
    
    
     String  user_agent = (String)http_properties.get( ClientIDGenerator.PR_USER_AGENT );
    
     if ( user_agent != null ){
      
       con.setRequestProperty("User-Agent", user_agent );
     }
    
     con.setRequestProperty("Connection", "close" );
    
     // some trackers support gzip encoding of replies
    
     con.addRequestProperty("Accept-Encoding","gzip");
    
     try{
      
       con.connect();
      
       InputStream is = null;
      
       try{
        
         is = con.getInputStream();
        
         String  resulting_url_str = con.getURL().toString();
        
         if ( !reqUrl.toString().equals( resulting_url_str )){
          
             // some kind of redirect has occurred. Unfortunately we can't get at the underlying
             // redirection reason (temp, perm etc) so we support the use of an explicit indicator
             // in the resulting url
          
           String  marker = "permredirect=1";
          
           int  pos = resulting_url_str.indexOf( marker );
        
           if ( pos != -1 ){
            
             pos = pos-1// include the '&' or '?'
            
             try{
               URL  redirect_url =
                 new URL( resulting_url_str.substring(0,pos));
              
                tracker_url[0= redirect_url;
           
             }catch( Throwable e ){
               Debug.printStackTrace(e);
             }
           }
         }
        
         String encoding = con.getHeaderField( "content-encoding");
        
         boolean  gzip = encoding != null && encoding.equalsIgnoreCase("gzip");
        
         // System.out.println( "encoding = " + encoding );
        
         if ( gzip ){
          
           is = new GZIPInputStream( is );
         }
        
           // there are some trackers out there that don't set content length correctly
           // so we can't reliably use it :(
        
         int content_length = -1; //con.getContentLength();
        
         //      System.out.println(length);
        
         byte[] data = new byte[1024];
        
         int  num_read = 0;
        
         // some trackers don't return content-length
        
         while ( content_length <= 0 || num_read < content_length ){
          
           try{
             int  len = is.read(data);
            
             if ( len > 0 ){
              
               message.write(data, 0, len);
              
               num_read += len;
              
               if ( num_read > 128*1024 ){
                
                   // someone's sending us junk, bail out
                
                 message.reset();
                
                 throw( new Exception( "Tracker response invalid (too large)" ));
                
               }
             }else if ( len == 0 ){
              
               Thread.sleep(20);
              
             }else{
              
               break;
             }
            
           }catch (Exception e){
            
             if (Logger.isEnabled()) {
              Logger.log(new LogEvent(torrent, LOGID,
                  "Exception while Requesting Tracker", e));
              Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
                  "Message Received was : " + message));
            }
            
             failure_reason = exceptionToString( e );
            
             break;
           }
         }
        
         if (Logger.isEnabled())
          Logger.log(new LogEvent(torrent, LOGID, "Tracker Announcer ["
              + lastUsedUrl + "] has received : " + message));
        
        
       }catch (Exception e){
        
         // e.printStackTrace();
        
         failure_reason = exceptionToString( e );
        
       }finally{
        
         if (is != null) {
          
           try {
             is.close();
            
           }catch (Exception e) {
           }
          
           is = null;
         }
       }
     }finally{
       con.disconnect();
     }
    
     return( failure_reason );
   }
  
   protected String
   announceUDP(
     URL            original_reqUrl,
    ByteArrayOutputStream  message,
    boolean                 is_probe )
  
     throws IOException
   {
     long timeout = is_probe?10000:PRUDPPacket.DEFAULT_UDP_TIMEOUT;
    
     URL reqUrl = TRTrackerUtils.adjustURLForHosting( original_reqUrl );

     if ( reqUrl != original_reqUrl ){
      if (Logger.isEnabled()){
        Logger.log(new LogEvent(torrent, LOGID,
            "    UDP: url adjusted to " + reqUrl ));
      }
     }
    
     String  failure_reason = null;
   
     PasswordAuthentication  auth = null
    
     try{
       if ( (!is_probe) && UrlUtils.queryHasParameter(reqUrl.getQuery(), "auth", false)) {
          auth = SESecurityManager.getPasswordAuthentication( UDP_REALM, reqUrl );
       }
            
       PRUDPPacketHandler handler = PRUDPPacketHandlerFactory.getHandler( UDPNetworkManager.getSingleton().getUDPNonDataListeningPortNumber());
      
       InetSocketAddress destination = new InetSocketAddress(reqUrl.getHost(),reqUrl.getPort()==-1?80:reqUrl.getPort());

       handler = handler.openSession( destination );
      
       try{
        
         for (int retry_loop=0;retry_loop<PRUDPPacketTracker.DEFAULT_RETRY_COUNT;retry_loop++){
          
           try{
        
             PRUDPPacket connect_request = new PRUDPPacketRequestConnect();
            
             PRUDPPacket reply = handler.sendAndReceive( auth, connect_request, destination, timeout );
            
             if ( reply.getAction() == PRUDPPacketTracker.ACT_REPLY_CONNECT ){
            
               PRUDPPacketReplyConnect connect_reply = (PRUDPPacketReplyConnect)reply;
              
               long  my_connection = connect_reply.getConnectionId();
            
               PRUDPPacketRequest request;
              
               if ( PRUDPPacketTracker.VERSION == 1 ){
                
                 PRUDPPacketRequestAnnounce announce_request = new PRUDPPacketRequestAnnounce( my_connection );
          
                 request = announce_request;
                
                   // bit of a hack this...
                
                 String  url_str = reqUrl.toString();
                
                 int    p_pos = url_str.indexOf("?");
                
                 url_str  = url_str.substring(p_pos+1);
                
                 String event_str = getURLParam( url_str, "event" );
                
                 int  event = PRUDPPacketRequestAnnounce.EV_UPDATE;
                
                 if ( event_str != null ){
                  
                   if ( event_str.equals( "started" )){
                    
                     event = PRUDPPacketRequestAnnounce.EV_STARTED;
                    
                   }else if ( event_str.equals( "stopped" )){
                    
                     event = PRUDPPacketRequestAnnounce.EV_STOPPED;
                    
                   }else if ( event_str.equals( "completed" )){
                    
                     event = PRUDPPacketRequestAnnounce.EV_COMPLETED;
                   }
                 }
                
                 String  ip_str = getURLParam( url_str, "ip" );
                
                 int  ip = 0;
                
                 if ( ip_str != null ){
                  
                   ip = PRHelpers.addressToInt( ip_str);
                 }
                
                 announce_request.setDetails(
                   torrent_hash.getBytes(),
                   tracker_peer_id,
                  getLongURLParam( url_str, "downloaded" ),
                  event,
                  ip,
                  (int)getLongURLParam( url_str, "numwant" ),
                  getLongURLParam( url_str, "left" ),
                  (short)getLongURLParam( url_str, "port" ),
                  getLongURLParam( url_str, "uploaded" ));
              
               }else{
                 PRUDPPacketRequestAnnounce2 announce_request = new PRUDPPacketRequestAnnounce2( my_connection );
                
                 request = announce_request;
                
                 // bit of a hack this...
                
                 String  url_str = reqUrl.toString();
                
                 int    p_pos = url_str.indexOf("?");
                
                 url_str  = url_str.substring(p_pos+1);
                
                 String event_str = getURLParam( url_str, "event" );
                
                 int  event = PRUDPPacketRequestAnnounce.EV_UPDATE;
                
                 if ( event_str != null ){
                  
                   if ( event_str.equals( "started" )){
                    
                     event = PRUDPPacketRequestAnnounce.EV_STARTED;
                    
                   }else if ( event_str.equals( "stopped" )){
                    
                     event = PRUDPPacketRequestAnnounce.EV_STOPPED;
                    
                   }else if ( event_str.equals( "completed" )){
                    
                     event = PRUDPPacketRequestAnnounce.EV_COMPLETED;
                   }
                 }
                
                 String  ip_str = getURLParam( url_str, "ip" );
                
                 int  ip = 0;
                
                 if ( ip_str != null ){
                  
                   ip = PRHelpers.addressToInt( ip_str);
                 }
                
                 announce_request.setDetails(
                   torrent_hash.getBytes(),
                   tracker_peer_id,
                  getLongURLParam( url_str, "downloaded" ),
                  event,
                  ip,
                  helper.getUDPKey(),
                  (int)getLongURLParam( url_str, "numwant" ),
                  getLongURLParam( url_str, "left" ),
                  (short)getLongURLParam( url_str, "port" ),
                  getLongURLParam( url_str, "uploaded" ))
               }
              
               reply = handler.sendAndReceive( auth, request, destination );
            
               if ( reply.getAction() == PRUDPPacketTracker.ACT_REPLY_ANNOUNCE ){
                
                 if ( auth != null ){
                  
                   SESecurityManager.setPasswordAuthenticationOutcome( UDP_REALM, reqUrl, true );
                 }
                
                 if ( PRUDPPacketTracker.VERSION == 1 ){
                   PRUDPPacketReplyAnnounce  announce_reply = (PRUDPPacketReplyAnnounce)reply;
                  
                   Map  map = new HashMap();
                  
                   map.put( "interval", new Long( announce_reply.getInterval()));
                  
                   int[]  addresses   = announce_reply.getAddresses();
                   short[]  ports    = announce_reply.getPorts();
                  
                   List  peers = new ArrayList();
                  
                   map.put( "peers", peers );
                  
                   for (int i=0;i<addresses.length;i++){
                    
                     Map  peer = new HashMap();
                    
                     peers.add( peer );
                    
                     peer.put( "ip", PRHelpers.intToAddress(addresses[i]).getBytes());
                     peer.put( "port", new Long( ports[i]));
                   }
                  
                   byte[] data = BEncoder.encode( map );
                  
                   message.write( data );
                  
                   return( null );
                  
                 }
                
                   PRUDPPacketReplyAnnounce2  announce_reply = (PRUDPPacketReplyAnnounce2)reply;
                  
                   Map  map = new HashMap();
                  
                   map.put( "interval", new Long( announce_reply.getInterval()));
                  
                   int[]  addresses   = announce_reply.getAddresses();
                   short[]  ports    = announce_reply.getPorts();
                  
                   map.put( "complete", new Long(announce_reply.getSeeders()));
                   map.put( "incomplete", new Long(announce_reply.getLeechers()));
                  
                   List  peers = new ArrayList();
                  
                   map.put( "peers", peers );
                  
                   for (int i=0;i<addresses.length;i++){
                    
                     Map  peer = new HashMap();
                    
                     peers.add( peer );
                    
                     peer.put( "ip", PRHelpers.intToAddress(addresses[i]).getBytes());
                     peer.put( "port", new Long( ports[i]));
                   }
                  
                   byte[] data = BEncoder.encode( map );
                  
                   message.write( data );
                  
                   return( null );
                
               }
            
               failure_reason = ((PRUDPPacketReplyError)reply).getMessage();
              
             }else{
              
               failure_reason = ((PRUDPPacketReplyError)reply).getMessage();
             }
           }catch( PRUDPPacketHandlerException e ){
            
             if ( e.getMessage() == null || e.getMessage().indexOf("timed out") == -1 ){
              
               throw( e );
             }
           }
         }
       }finally{
      
         handler.closeSession();
       }
      
     }catch( Throwable e ){
    
       failure_reason = exceptionToString(e);
     }
    
    if ( auth != null ){
         
      SESecurityManager.setPasswordAuthenticationOutcome( UDP_REALM, reqUrl, false );
    }

     return( failure_reason );
   }
  
   protected long
   getLongURLParam(
     String    url,
    String    param )
   {
     String  val = getURLParam( url, param );
    
     if( val == null ){
      
       return(0);
     }
    
     return( Long.parseLong( val ));
   }
  
   protected String
   getURLParam(
     String    url,
    String    param )
   {
     int  p1 = url.indexOf( param + "=" );
    
     if ( p1 == -1 ){
      
       return( null );
     }
    
     int  p2 = url.indexOf( "&", p1 );
    
     if ( p2 == -1 ){
      
       return( url.substring(p1+param.length()+1));
     }
    
     return( url.substring(p1+param.length()+1,p2));
   }
  
  protected String
  exceptionToString(
    Throwable   e )
  {
    String class_name = e.getClass().getName();
   
    int  pos = class_name.lastIndexOf( '.' );
   
    if ( pos != -1 ){
     
      class_name = class_name.substring(pos+1);
    }
   
    String str = class_name + ":" + Debug.getNestedExceptionMessage(e);
   
    if ( str.indexOf( "timed out") != -1 ){
     
      str  = "timeout";
    }
   
    return( str );
  }
 
  public URL
  constructUrl(
    String   evt,
  URL    _url)
 
    throws Exception
  {
    String  url = _url.toString();
   
    StringBuffer request = new StringBuffer(url);
   
      // if url already has a query component then just append our parameters on the end
    if ( url.indexOf('?') != -1 ){
      request.append('&');
    }else{
      request.append('?');
    }
   
      // the client-id stuff RELIES on info_hash being the FIRST parameter added by
      // us to the URL, so don't change it!
   
    request.append(info_hash);
    request.append(tracker_peer_id_str);
   
  String  port_details =
    announce_data_provider.getCryptoLevel()==NetworkManager.CRYPTO_OVERRIDE_REQUIRED?
        TRTrackerUtils.getPortsForURLFullCrypto():
        TRTrackerUtils.getPortsForURL();

    request.append(port_details);
    request.append("&uploaded=").append(announce_data_provider.getTotalSent());
    request.append("&downloaded=").append(announce_data_provider.getTotalReceived());
   
    if ( Constants.DOWNLOAD_SOURCES_PRETEND_COMPLETE ){
     
       request.append("&left=0");
     
    }else{
   
      request.append("&left=").append(announce_data_provider.getRemaining());
    }
   
      // 3017: added at request of tracker admins who want to be able to monitor swarm poisoning
   
    request.append("&corrupt=").append(announce_data_provider.getFailedHashCheck());
 
   
    //TrackerID extension
    if( tracker_id.length() > 0 ) {
      request.append( "&trackerid=").append(tracker_id );
    }
   
   
    if (evt.length() != 0) {
      request.append("&event=").append(evt);
    }
   
    boolean  stopped = evt.equals("stopped");
   
    if ( stopped ){
     
      request.append("&numwant=0");
     
      if ( stopped_for_queue ){
       
        request.append( "&azq=1" );
      }
    }else {
     
      //calculate how many peers we should ask for
     
      int numwant = Math.min(calculateNumWant(),userMaxNumwant);


      request.append("&numwant=").append(numwant);
     
      //no_peer_id has been made obsolete by 'compact'
    }
   
      // actually, leave this in, ask PARG why!
   
    request.append("&no_peer_id=1");
     
      // latest space saving measure, a compact return type where peers are returned
      // as 6 byte entries in a single byte[] (4 bytes ip, 2 byte port)
      // leave this as always supplied, ask PARG why
   
    request.append( "&compact=1" );
 
      // any explicit override takes precedence over any implicit override added
      // when hosting torrents
   
    String explicit_ips = COConfigurationManager.getStringParameter( "Override Ip", "" );
   
    String   ip          = null;
    String  tracker_network  = AENetworkClassifier.categoriseAddress( _url.getHost());
   
      // make sure this tracker network is enabled
   
    boolean  network_ok      = false;
    boolean  normal_network_ok  = false;
   
    if ( peer_networks == null ){
     
      network_ok      = true;
      normal_network_ok  = true;
     
    }else{
      for (int i=0;i<peer_networks.length;i++){
     
        if ( peer_networks[i] == AENetworkClassifier.AT_PUBLIC ){
         
          normal_network_ok = true;
        }
       
        if ( peer_networks[i] == tracker_network ){
         
          network_ok  = true;
        }
      }
    }
   
    if ( !network_ok ){
     
      throw( new Exception( "Network not enabled for url '" + _url + "'" ));
    }
   
    String  normal_explicit = null;
   
     if ( explicit_ips.length() > 0 ){
       
         // gotta select an appropriate override based on network type
         
    StringTokenizer  tok = new StringTokenizer( explicit_ips, ";" );
       
    while( tok.hasMoreTokens()){
     
      String  this_address = tok.nextToken().trim();
     
      if ( this_address.length() > 0 ){
       
        String  cat = AENetworkClassifier.categoriseAddress( this_address );
       
        if ( cat == AENetworkClassifier.AT_PUBLIC ){
         
          normal_explicit  = this_address;
        }
       
        if ( tracker_network == cat ){
         
          ip = this_address;
         
          break;
        }
      }
    } 
     }
   
     if ( ip == null ){
      
         // if we have a normal explicit override and this is enabled then use it
      
       if ( normal_network_ok && normal_explicit != null ){
        
         ip = normal_explicit;
        
       }else{
        
         if ( ip_override != null ){
          
           ip = ip_override;
         }
       }
     }
   
    if ( ip != null ){
           
      if ( tracker_network == AENetworkClassifier.AT_PUBLIC ){
     
        try{
          ip = PRHelpers.DNSToIPAddress( ip );
       
        }catch( UnknownHostException e){
                 
          if (Logger.isEnabled())
            Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
                "IP Override host resolution of '" + ip
                    + "' fails, using unresolved address"));
        }
      }
           
      request.append("&ip=").append(ip);
    }
 
    if ( COConfigurationManager.getBooleanParameter("Tracker Key Enable Client", true )){
       
        request.append( "&key=").append( helper.getTrackerKey());
    }
   
  String  ext = announce_data_provider.getExtensions();
 
  if ( ext != null ){
   
      // sanitise it
   
    while ( ext.startsWith("&")){
     
      ext = ext.substring( 1 );
    }
   
    request.append( "&" );
   
    request.append( ext );
  }
 
  request.append( "&azver=" + TRTrackerAnnouncer.AZ_TRACKER_VERSION_CURRENT );
 
  if ( az_tracker && !stopped ){
   
    int up = announce_data_provider.getUploadSpeedKBSec( evt.equals( "started" ));
   
    if ( up > 0 ){
     
      request.append( "&azup=" + up );
    }
   
    String as = NetworkAdmin.getSingleton().getCurrentASN().getAS();
   
      if ( as.length() > 0 ){
       
        request.append( "&azas=" + URLEncoder.encode( as, "UTF8" ));
      }
     
    DHTNetworkPosition  best_position = DHTNetworkPositionManager.getBestLocalPosition();
   
    if ( best_position != null ){
     
      try{
        byte[]  bytes = DHTNetworkPositionManager.serialisePosition( best_position );
       
        request.append( "&aznp=" + Base32.encode( bytes ));
               
      }catch( Throwable e ){
       
        Debug.printStackTrace( e );
      }
    }
  }
 
    return new URL( request.toString());
  }

  protected int
  calculateNumWant()
  {
  if ( !announce_data_provider.isPeerSourceEnabled( PEPeerSource.PS_BT_TRACKER )){
   
    return( 0 );
  }

    int MAX_PEERS = 100;
   
      // ask for a bit more than our max to allow for connections failures
   
    int maxAllowed = 3*announce_data_provider.getMaxNewConnectionsAllowed()/2;
   
    if ( maxAllowed < 0 || maxAllowed > MAX_PEERS ) {
      maxAllowed = MAX_PEERS;
    }

    return maxAllowed;
  }
 
  public byte[]
  getPeerId()
  {
    return( data_peer_id );
  }

   public void
   setAnnounceDataProvider(
     TRTrackerAnnouncerDataProvider _provider)
   {
    try{
      this_mon.enter();

      announce_data_provider = _provider;
     
    }finally{
     
      this_mon.exit();
    }
   }
 
  public TOTorrent
  getTorrent()
  {
    return( torrent );
  }
 
  public URL
  getTrackerURL()
  {
    return( lastUsedUrl );
  }
 
  public void
  setTrackerURL(
    URL new_url )
  {
    try{
      new_url = new URL( new_url.toString().replaceAll(" ", ""));
     
      List list = new ArrayList(1);
     
      list.add( new_url );
     
      trackerUrlLists.clear();
     
      trackerUrlLists.add( list );
   
      informURLChange( lastUsedUrl, new_url, true );  
     
    }catch( Throwable e ){
     
      Debug.printStackTrace(e);
    }
  }
 
  public void
  setAnnounceSets(
    TOTorrentAnnounceURLSet[]    _set )
  {
    announce_urls = _set;
  }
 
  public TOTorrentAnnounceURLSet[]
  getAnnounceSets()
  {
    return( announce_urls );
  }
 
  public void
  resetTrackerUrl(
    boolean    shuffle )
  {
    String  old_list = trackerURLListToString();
   
    constructTrackerUrlLists(shuffle);
  
    if ( trackerUrlLists.size() == 0 ){
   
      return;
    }
 
    if ( !old_list.equals(trackerURLListToString())){
     
      URL  first_url = (URL)((List)trackerUrlLists.get(0)).get(0);
     
      informURLChange( lastUsedUrl, first_url, true );
    }
  }
 
  public void
  refreshListeners()
  {
    informURLRefresh();
  }

  public void
  setIPOverride(
    String    override )
  {
    ip_override = override;
  }
 
  public void
  clearIPOverride()
  {
    ip_override = null;
  }
 
  private void
  constructTrackerUrlLists(
    boolean  shuffle )
  {
    try{
      trackerUrlLists = new ArrayList(1);
   
        //This entry is present on multi-tracker torrents
            
      if ( announce_urls.length == 0 ){
     
          //If not present, we use the default specification
         
        URL url = torrent.getAnnounceURL();
              
          //We then contruct a list of one element, containing this url, and put this list
          //into the list of lists of urls.
         
        List list = new ArrayList();
       
        list.add(url);
       
        trackerUrlLists.add(list);
      }else{
         
          //Ok we have a multi-tracker torrent
       
        for(int i = 0 ; i < announce_urls.length ; i++){
         
            //Each list contains a list of urls
         
          URL[]  urls = announce_urls[i].getAnnounceURLs();
         
           List random_urls = new ArrayList();
          
           for(int j = 0 ; j < urls.length; j++){
             
            //System.out.println(urls.get(j).getClass());
                 
            URL url = urls[j];
                           
              //Shuffle
             
            int pos = shuffle?(int)(Math.random() (random_urls.size()+1)):j;
           
            random_urls.add(pos,url);
            }
                            
              //Add this list to the list
             
           trackerUrlLists.add(random_urls);
        }
      }     
    }catch(Exception e){
     
      Debug.printStackTrace( e );
    }
  }
 
  protected String
  trackerURLListToString()
  {
    String trackerUrlListString = "[";
   
    for (int i=0;i<trackerUrlLists.size();i++){

      List  group = (List)trackerUrlLists.get(i);
     
      trackerUrlListString  += (i==0?"":",") + "[";
     
      for (int j=0;j<group.size();j++){
       
        URL  u = (URL)group.get(j);
       
        trackerUrlListString  += (j==0?"":",") + u.toString();
      }
     
      trackerUrlListString  += "]";
    }
   
    trackerUrlListString += "]";
   
    return( trackerUrlListString );
  }
 
    protected TRTrackerAnnouncerResponseImpl
    decodeTrackerResponse(
      URL      url,
      byte[]    data )
    {
      String  failure_reason;
     
      if ( data == null ){
       
        failure_reason = "no response";
       
      }else{
     
       try{
             //parse the metadata
       
         try{
           Map metaData = BDecoder.decode(data);
          
           // obey any peers source restrictions
          
           Object o = metaData.get( "az_ps" );

           if ( o instanceof List ){

             List peer_sources = (List)o;

             List  x = new ArrayList();
            
             for (int i=0;i<peer_sources.size();i++){
              
               Object o1 = peer_sources.get(i);
              
               if ( o1 instanceof byte[] ){
                
                 x.add( new String((byte[])o1));
               }
             }
            
             String[]  y = new String[x.size()];
            
             x.toArray( y );
            
             announce_data_provider.setPeerSources( y );
           }
           
             // handle any user warnings in the response
         
           try{
             byte[]  b_warning_message = (byte[])metaData.get( "warning message" );
          
             if (   b_warning_message != null &&
                COConfigurationManager.getBooleanParameter( "Tracker Client Show Warnings" )){

               String  warning_message = new String(b_warning_message);
              
                // don't report the same message twice per torrent
             
              if ( !warning_message.equals( last_tracker_message )){
               
                last_tracker_message  = warning_message;
             
                boolean  log_it = false;
               
                  // only report a given message once per tracker
               
                try{
                  class_mon.enter();
               
                  String last_warning_message = (String)tracker_report_map.get( url.getHost());
                 
                  if (   last_warning_message == null ||
                      !warning_message.equals( last_warning_message )){
                  
                    log_it  = true;
                   
                    tracker_report_map.put( url.getHost(), warning_message );
                  }
                }finally{
                 
                  class_mon.exit();
                }
               
                if ( log_it ){
                   Logger.logTextResource(new LogAlert(torrent,
                       LogAlert.UNREPEATABLE, LogAlert.AT_WARNING,
                      "TrackerClient.announce.warningmessage"), new String[] {
                      announce_data_provider.getName(), warning_message });
                 }
              }
             }
           }catch( Throwable e ){
            
             Debug.printStackTrace( e );
           }
          
          long  time_to_wait;
                   
          try {
            tracker_interval = time_to_wait = ((Long) metaData.get("interval")).longValue();
           
            Long raw_min_interval = (Long) metaData.get("min interval");

            if (Logger.isEnabled()) {
              Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_INFORMATION,
                  "Received from announce: 'interval' = " + time_to_wait
                      + "; 'min interval' = " + raw_min_interval));
            }

            // guard against crazy return values
            if (time_to_wait < 0 || time_to_wait > 0xffffffffL) {
              time_to_wait = 0xffffffffL;
            }

            if (raw_min_interval != null) {
              tracker_min_interval = min_interval = raw_min_interval.longValue();
             
              // ignore useless values
              // Note: Many trackers set min_interval and interval the same.
              if (min_interval < 1) {
                if (Logger.isEnabled()) {
                  Logger.log(new LogEvent(
                      torrent,
                      LOGID,
                      LogEvent.LT_INFORMATION,
                      "Tracker being silly and "
                          + "returning a 'min interval' of less than 1 second ("
                          + min_interval + ")"));
                }
                min_interval = 0;
              } else if (min_interval > time_to_wait) {
                if (Logger.isEnabled()) {
                  Logger.log(new LogEvent(
                      torrent,
                      LOGID,
                      LogEvent.LT_INFORMATION,
                      "Tracker being silly and "
                          + "returning a 'min interval' ("
                          + min_interval
                          + ") greater than recommended announce 'interval'"
                          + " (" + time_to_wait + ")"));
                }
                min_interval = 0;
              }
            } else {
              // tracker owners complain we announce too much but then never
              // implement "min interval".  So take it into our own hands
              // and enforce a min_interval of interval when there is no
              // "min interval"
              min_interval = time_to_wait > 30 ? time_to_wait - 10 : time_to_wait;
            }
           
            if(userMinInterval != 0)
            {
              time_to_wait = Math.max(userMinInterval, time_to_wait);
              min_interval = Math.max(min_interval, userMinInterval);
              if (Logger.isEnabled()) {
                Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_INFORMATION,
                    "Overriding with user settings: 'interval' = " + time_to_wait
                        + "; 'min interval' = " + min_interval));
              }
            }


            // roll back 10 seconds to make sure we announce before the tracker
            // times us out.  This is done after min_interval in order not to
            // mess up the "ignore useless values"
            if (time_to_wait > 30)
              time_to_wait -= 10;

          } catch (Exception e) {
            
                 byte[]  failure_reason_bytes = (byte[]) metaData.get("failure reason");
               
                 if ( failure_reason_bytes == null ){
                 
                   if (Logger.isEnabled())
                    Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_WARNING,
                        "Problems with Tracker, will retry in "
                            + getErrorRetryInterval() + "ms"));
                                  
                   return( new TRTrackerAnnouncerResponseImpl( url, torrent_hash, TRTrackerAnnouncerResponse.ST_OFFLINE, getErrorRetryInterval(), "Unknown cause" ));
     
                 }
                  
                   // explicit failure from the tracker
                
                 failure_reason = new String( failure_reason_bytes, Constants.DEFAULT_ENCODING);
                                   
                 return( new TRTrackerAnnouncerResponseImpl( url, torrent_hash, TRTrackerAnnouncerResponse.ST_REPORTED_ERROR, getErrorRetryInterval(), failure_reason ));
            
           }
          
             //System.out.println("Response from Announce: " + new String(data));
          
           Long incomplete_l   = getLong( metaData, "incomplete");
           Long complete_l     = getLong( metaData, "complete");
           Long downloaded_l   = getLong( metaData, "downloaded");
          
           if ( incomplete_l != null || complete_l != null  ){
          
             if (Logger.isEnabled())
              Logger.log(new LogEvent(torrent, LOGID,
                  "ANNOUNCE SCRAPE1: seeds=" + complete_l + " peers="
                      + incomplete_l));
           }
          
          
                 //TrackerID extension, used by phpbt trackers.
                 //We reply with '&trackerid=1234' when we receive
                 //'10:tracker id4:1234e' on announce reply.
                 //NOTE: we receive as 'tracker id' but reply as 'trackerid'
                 byte[] trackerid = (byte[])metaData.get( "tracker id" );
                 if( trackerid != null ) {
                   tracker_id = new String( trackerid );
                 }
                
            byte[]  crypto_flags = (byte[])metaData.get( "crypto_flags" );
                      
            //build the list of peers
          List valid_meta_peers = new ArrayList();
           
            Object  meta_peers_peek = metaData.get( "peers" );
           
           
            Long  az_compact_l   = (Long)metaData.get( "azcompact" );
            long  az_compact    = az_compact_l==null?0:az_compact_l.longValue();

          boolean  this_is_az_tracker = az_compact == 2;
         
          if ( az_tracker != this_is_az_tracker || lastUsedUrl != lastAZTrackerCheckedURL ){
             
            lastAZTrackerCheckedURL = lastUsedUrl;
           
            az_tracker  = this_is_az_tracker;
           
            TRTrackerUtils.setAZTracker( url, az_tracker );
          }

            if ( az_compact == 2 ){
             
 
                // latest return to dictionary based data
             
            List meta_peers = (List)meta_peers_peek;

            int peers_length = meta_peers.size();

             if (Logger.isEnabled()) {
                Logger.log(new LogEvent(torrent, LOGID,
                    "ANNOUNCE CompactPeers2: num=" + peers_length));
             }

            if ( peers_length > 1 ){
                // calculate average rtt to use for those with no rtt
               
              long  total_rtt   = 0;
              int    rtt_count  = 0;
             
              for ( int i = 0; i < peers_length; i++ ){
                
                Map peer = (Map) meta_peers.get(i);
               
                  Long  l_rtt = (Long)peer.get( "r" );
 
                  if ( l_rtt != null ){
                   
                    long rtt = l_rtt.longValue();
                   
                    if ( rtt <= 0 ){
                     
                        // invalid, remove
                     
                      peer.remove( "r" );
                     
                    }else{
                     
                      total_rtt   += rtt;
                    }
                   
                    rtt_count++;
                  }
              }
             
              final int average_rtt = (int)( rtt_count==0?0:(total_rtt/rtt_count));
             
                // sort into smallest rtt order with biased at front
             
              Collections.sort(
                  meta_peers,
                  new Comparator()
                  {
                    public int
                    compare(
                      Object  o1,
                      Object  o2 )
                    {
                      Map  map1 = (Map)o1;
                      Map map2 = (Map)o2;
                     
                        Long  l_rtt1 = (Long)map1.get( "r" );
                        Long  l_rtt2 = (Long)map2.get( "r" );
 
                        boolean  biased_1 = map1.containsKey( "b" );
                        boolean  biased_2 = map2.containsKey( "b" );
                       
                        if ( biased_1 == biased_2 ){
                         
                          int  rtt1 = l_rtt1==null?average_rtt:l_rtt1.intValue();
                          int  rtt2 = l_rtt2==null?average_rtt:l_rtt2.intValue();
                         
                          return( rtt1 - rtt2 );
                         
                        }else if ( biased_1 ){
                         
                          return( -1 );
                        }else{
                         
                          return( +1 );
                        }
                    }
                  });
             
                // interleave non-biased peers with good rtt
             
              int  biased_pos    = peers_length;
              int  non_biased_pos  = peers_length;
             
              for ( int i = 0; i < peers_length; i++ ){
                
                Map peer = (Map) meta_peers.get(i);
     
                if ( peer.containsKey( "b" )){
                 
                  if ( i == 0 ){
                   
                    biased_pos  = i;
                  }
                }else{
                 
                  non_biased_pos = i;
                 
                  break;
                }
              }
               
              List  new_peers = new ArrayList(peers_length);
             
              int    non_biased_start = non_biased_pos;
             
              boolean  last_biased  = true;
               
              while( biased_pos < non_biased_start || non_biased_pos < peers_length ){
               
                if ( biased_pos < non_biased_start ){
                 
                  if ( non_biased_pos < peers_length ){
                   
                    Map biased     = (Map) meta_peers.get(biased_pos);
                    Map non_biased   = (Map) meta_peers.get(non_biased_pos);

                    boolean  use_biased;
                   
                    if ( !last_biased ){
                     
                      use_biased = true;
                     
                    }else{
                     
                        Long  l_rtt_biased     = (Long)biased.get( "r" );
                        Long  l_rtt_non_biased   = (Long)non_biased.get( "r" );
   
                        int  biased_rtt     = l_rtt_biased==null?average_rtt:l_rtt_biased.intValue();
                        int  non_biased_rtt   = l_rtt_non_biased==null?average_rtt:l_rtt_non_biased.intValue();
   
                        use_biased = non_biased_rtt >= biased_rtt;
                    }
                   
                    if ( use_biased ){
                     
                      new_peers.add( biased );
                     
                      biased_pos++;
                     
                    }else{
                     
                      new_peers.add( non_biased );
                     
                      non_biased_pos++;
                    }
                   
                    last_biased = use_biased;
                  }else{
                   
                    new_peers.addmeta_peers.get( biased_pos++ ));
                  }
                }else{
                 
                  new_peers.add( meta_peers.get( non_biased_pos++ ));
                }
              }
             
              meta_peers = new_peers;
            }
           
            for ( int i = 0; i < peers_length; i++ ){
              
              Map peer = (Map) meta_peers.get(i);
                  
              try{
                byte[]    ip_bytes = (byte[])peer.get("i");
 
                String  ip;
               
                if ( ip_bytes.length == 4 ){
                 
                    int  ip1 = 0xff & ip_bytes[0];
                    int  ip2 = 0xff & ip_bytes[1];
                    int  ip3 = 0xff & ip_bytes[2];
                    int  ip4 = 0xff & ip_bytes[3];
   
                    ip   = ip1 + "." + ip2 + "." + ip3 + "." + ip4;
                }else{
               
                  StringBuffer sb = new StringBuffer(39);
                 
                  for ( int j=0; j<16; j+=2 ){
                   
                    sb.append
                        Integer.toHexString(((ip_bytes[j]<<8) & 0xff00) | (ip_bytes[j+1]&0x00ff)));
                     
                      if (j < 14 ){
                       
                         sb.append( ":" );
                      }
                  }

                  ip = sb.toString();
                }
               
                  byte[]  tcp_bytes  = (byte[])peer.get("t");
                 
                  int    tcp_port   = ((tcp_bytes[0]&0xff) << 8 ) + (tcp_bytes[1]&0xff );
                 
                  byte[]  peer_peer_id = TRTrackerAnnouncerImpl.getAnonymousPeerId( ip, tcp_port );
 
                  int    udp_port   = 0;
                 
                  byte[]  udp_bytes = (byte[])peer.get("u");
               
                  if ( udp_bytes != null ){
                   
                    if ( udp_bytes.length == 0 ){
                     
                      udp_port = tcp_port;
                     
                    }else{
                     
                      udp_port  = ((udp_bytes[0]&0xff) << 8 ) + (udp_bytes[1]&0xff );
                    }
                  }
                 
                  int  http_port = 0;
                 
                  byte[]  http_bytes = (byte[])peer.get("h");

                  if ( http_bytes != null ){
                   
                    http_port  = ((http_bytes[0]&0xff) << 8 ) + (http_bytes[1]&0xff );
                  }
                 
                  short  protocol = DownloadAnnounceResultPeer.PROTOCOL_NORMAL;
                 
                  byte[]  protocol_bytes = (byte[])peer.get("c");
                 
                  if ( protocol_bytes != null ){
                   
                    protocol = (protocol_bytes[0]&0x01)==0?DownloadAnnounceResultPeer.PROTOCOL_NORMAL:DownloadAnnounceResultPeer.PROTOCOL_CRYPT;
                  }
                 
                  Long  l_azver = (Long)peer.get("v" );
                 
                  byte  az_ver = l_azver==null?TRTrackerAnnouncer.AZ_TRACKER_VERSION_1:l_azver.byteValue();
                                     
                  Long  l_up_speed = (Long)peer.get( "s" );
                 
                  boolean  biased = peer.containsKey("b");
                 
                  if ( biased ){
                   
                    PeerClassifier.setAzureusIP( ip );
                  }
                 
                TRTrackerAnnouncerResponsePeerImpl new_peer =
                  new TRTrackerAnnouncerResponsePeerImpl(
                    PEPeerSource.PS_BT_TRACKER,
                    peer_peer_id,
                    ip,
                    tcp_port,
                    udp_port,
                    http_port,
                    protocol,
                    az_ver,
                    l_up_speed==null?0:l_up_speed.shortValue());
                   
                if (Logger.isEnabled()){
                 
                    String  extra = "";
                   
                    Long  l_rtt = (Long)peer.get( "r" );
                       
                    if ( l_rtt != null ){
                     
                      extra = ",rtt=" + l_rtt;
                    }
                   
                    if ( biased ){
                     
                      extra += ",biased";
                    }
                   
                  Logger.log(new LogEvent(torrent, LOGID,
                      "AZ2-COMPACT PEER: " + new_peer.getString() + extra));
                }
               
                valid_meta_peers.add( new_peer );
               
              }catch( Throwable e ){
               
                  if (Logger.isEnabled())
                    Logger.log(
                      new LogEvent(
                        torrent, LOGID, LogEvent.LT_ERROR,
                        "Invalid az2 peer received: " + peer ));
              }
            }
           
            }else if ( meta_peers_peek instanceof List ){
             
                // old style non-compact
             
            List meta_peers = (List)meta_peers_peek;
                       
              //for every peer
           
            int peers_length = meta_peers.size();

             if (Logger.isEnabled()) {
                Logger.log(new LogEvent(torrent, LOGID,
                    "ANNOUNCE old style non-compact: num=" + peers_length));
             }
           
            if ( crypto_flags != null && peers_length != crypto_flags.length ){
             
              crypto_flags = null;
             
                             if (Logger.isEnabled())
                            Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
                                "Invalid crypto_flags returned: length mismatch" ));
            }
           
            for (int i = 0; i < peers_length; i++) {
                
              Map peer = (Map) meta_peers.get(i);
                            
              Object s_peerid  = peer.get("peer id");
              Object s_ip    = peer.get("ip");
              Object s_port  = peer.get("port");
                       
                // Assert that all ip and port are available
             
              if ( s_ip != null && s_port != null ){
         
                  //get the peer ip address
               
                String ip = new String((byte[]) s_ip, Constants.DEFAULT_ENCODING);
               
                  //get the peer port number - should be Long but have seen byte[] on occasion
               
                int peer_port = s_port instanceof byte[]?Integer.parseInt(new String((byte[])s_port)):((Long) s_port).intValue();
               
                                // try to repair invalid peer ports; worst that can happen is we
                                // still can't make outgoing connections that we already can't make
                                if (peer_port >65535)
                                    peer_port -=65536;
                                if (peer_port <0)
                                    peer_port +=65536;
                       
                                if (peer_port < 0 || peer_port > 65535) {
                                  if (Logger.isEnabled())
                                    Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
                                        "Invalid peer port given: " + ip + ": " + peer_port));
                                  continue;
                                }
               
                byte[] peer_peer_id;
               
                // extension - if peer id is missing then the tracker isn't sending
                // peer ids to save on bandwidth. However, we need something "unique" to
                // work on internally so make an ID up from the ip and port
               
                if ( s_peerid == null ){
                 
                  // Debug.out(ip + ": tracker did not give peerID in reply");

                  peer_peer_id = TRTrackerAnnouncerImpl.getAnonymousPeerId( ip, peer_port );
                 
                  // System.out.println("generated peer id" + new String(peerId) + "/" + ByteFormatter.nicePrint( peerId, true ));
                }else{
               
                  peer_peer_id = (byte[])s_peerid ;
                }
               
                short   protocol;
               
                if ( crypto_flags == null ){
                 
                  protocol   = DownloadAnnounceResultPeer.PROTOCOL_NORMAL;
                 
                }else{
                 
                  protocol = crypto_flags[i]==0?DownloadAnnounceResultPeer.PROTOCOL_NORMAL:DownloadAnnounceResultPeer.PROTOCOL_CRYPT;
                }
               
                int    udp_port  = 0;
                int    http_port  = 0;
               
                TRTrackerAnnouncerResponsePeerImpl new_peer =
                  new TRTrackerAnnouncerResponsePeerImpl(
                    PEPeerSource.PS_BT_TRACKER,
                    peer_peer_id,
                    ip,
                    peer_port,
                    udp_port,
                    http_port,
                    protocol,
                    TRTrackerAnnouncer.AZ_TRACKER_VERSION_1,
                    (short)0 );
                   
                if (Logger.isEnabled())
                  Logger.log(new LogEvent(torrent, LOGID,
                      "NON-COMPACT PEER: " + new_peer.getString()));

                valid_meta_peers.add( new_peer );

               
              }
            }
            }else if ( meta_peers_peek instanceof byte[] ){
             
                // byte[] for compact returns
             
           
              byte[]  meta_peers = (byte[])meta_peers_peek;
                           
              int  entry_size = az_compact==1?9:6;
             
              if ( crypto_flags != null && meta_peers.length/entry_size != crypto_flags.length ){
             
              crypto_flags = null;
             
                             if (Logger.isEnabled())
                            Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
                                "Invalid crypto_flags returned: length mismatch" ));
            }

              int  peer_number = 0;
             
               if (Logger.isEnabled()) {
                  Logger.log(new LogEvent(torrent, LOGID,
                      "ANNOUNCE CompactPeers: num=" + (meta_peers.length/entry_size)));
               }

             
              for (int i=0;i<meta_peers.length;i+=entry_size){
               
                peer_number++;
               
                int  ip1 = 0xFF & meta_peers[i];
                int  ip2 = 0xFF & meta_peers[i+1];
                int  ip3 = 0xFF & meta_peers[i+2];
                int  ip4 = 0xFF & meta_peers[i+3];
                int  po1 = 0xFF & meta_peers[i+4];
                int  po2 = 0xFF & meta_peers[i+5];
               
                String  ip       = "" + ip1 + "." + ip2 + "." + ip3 + "." + ip4;
                int    tcp_port   = po1*256+po2;
               
                if (tcp_port < 0 || tcp_port > 65535) {
                  if (Logger.isEnabled())
                    Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
                        "Invalid compact peer port given: " + ip + ": "
                        + tcp_port));
                  continue;
                }
               
                byte[]  peer_peer_id = TRTrackerAnnouncerImpl.getAnonymousPeerId( ip, tcp_port );
             
                short   protocol;
                int    udp_port;
               
                if ( az_compact == 1 ){
                 
                  int  upo1 = 0xFF & meta_peers[i+6];
                  int  upo2 = 0xFF & meta_peers[i+7];
                 
                  udp_port   = upo1*256+upo2;

                  byte  flags = meta_peers[i+8];
                 
                  protocol = (flags&0x01)==0?DownloadAnnounceResultPeer.PROTOCOL_NORMAL:DownloadAnnounceResultPeer.PROTOCOL_CRYPT;
                 
                }else{
                 
                 
                if ( crypto_flags == null ){
                 
                    protocol   = DownloadAnnounceResultPeer.PROTOCOL_NORMAL;

                }else{
                 
                  protocol = crypto_flags[peer_number-1]==0?DownloadAnnounceResultPeer.PROTOCOL_NORMAL:DownloadAnnounceResultPeer.PROTOCOL_CRYPT;
                }
               
                  udp_port  = 0;
                }           

                int  http_port = 0;
               
                TRTrackerAnnouncerResponsePeerImpl peer =
                  new TRTrackerAnnouncerResponsePeerImpl(
                    PEPeerSource.PS_BT_TRACKER,
                    peer_peer_id,
                    ip,
                    tcp_port,
                    udp_port,
                    http_port,
                    protocol,
                    TRTrackerAnnouncer.AZ_TRACKER_VERSION_1,
                    (short)0 );
               
                if (Logger.isEnabled())
                Logger.log(new LogEvent(torrent, LOGID, "COMPACT PEER: " + peer.getString()));

                valid_meta_peers.add( peer );
              }
            }else if ( meta_peers_peek instanceof Map ){

                // some trackers incorrectly return an empty Map when no peers available
             
              if (((Map)meta_peers_peek).size() != 0 ){
             
              throw( new IOException( "peers missing from response" ));
              }
            }else if (!metaData.containsKey("peers6")){
             
                //   we got nothing useful under peers and no peers6 either
             
            throw( new IOException( "peers missing from response" ));
            }
           
           
            final byte[] v6peers = (byte[])metaData.get("peers6");
           
            if ( v6peers != null ){
             
                // 16 bytes for v6 + 2 bytes for port
             
            final int entry_size = 18;
   
            final byte[] rawAddr = new byte[16];
           
            for (int i=0; i<v6peers.length; i+=entry_size ){
             
              System.arraycopy( v6peers, i, rawAddr, 0, 16 );
             
              String ip = InetAddress.getByAddress(rawAddr).getHostAddress();
             
                int  po1 = 0xFF & v6peers[i+16];
                int  po2 = 0xFF & v6peers[i+17];

              int tcp_port = po1*256 + po2;
             
              if (tcp_port < 0 || tcp_port > 65535){
             
                if (Logger.isEnabled()){
                 
                  Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR, "Invalid compactv6 peer port given: " + ip + ": " + tcp_port));
                }
               
                continue;
              }
             
              byte[] peer_peer_id = TRTrackerAnnouncerImpl.getAnonymousPeerId( ip, tcp_port );
             
              short protocol = DownloadAnnounceResultPeer.PROTOCOL_NORMAL;
             
              TRTrackerAnnouncerResponsePeerImpl peer = new TRTrackerAnnouncerResponsePeerImpl(PEPeerSource.PS_BT_TRACKER, peer_peer_id, ip, tcp_port, 0, 0, protocol, TRTrackerAnnouncer.AZ_TRACKER_VERSION_1, (short) 0);
             
              if (Logger.isEnabled()){
               
                Logger.log(new LogEvent(torrent, LOGID, "COMPACTv6 PEER: " + peer.getString()));
              }
             
              valid_meta_peers.add(peer);
            }
          }
           
           
          TRTrackerAnnouncerResponsePeerImpl[] peers=new TRTrackerAnnouncerResponsePeerImpl[valid_meta_peers.size()];
         
          valid_meta_peers.toArray(peers);
         
          helper.addToTrackerCache( peers);
         
          TRTrackerAnnouncerResponseImpl resp = new TRTrackerAnnouncerResponseImpl( url, torrent_hash, TRTrackerAnnouncerResponse.ST_ONLINE, time_to_wait, peers );
         
            //reset failure retry interval on successful connect
         
          failure_added_time = 0;
         
          Map extensions = (Map)metaData.get( "extensions" );
         
          resp.setExtensions(extensions);
         
          if ( extensions != null ){
           
            if ( complete_l == null) {
              complete_l = (Long)extensions.get("complete");
            }
           
            if ( incomplete_l == null) {
              incomplete_l = (Long)extensions.get("incomplete");
            }
         
            if (Logger.isEnabled())
              Logger.log(new LogEvent(torrent, LOGID,
                  "ANNOUNCE SCRAPE2: seeds=" + complete_l + " peers="
                      + incomplete_l));
                 
            Object  override = extensions.get( "min interval override" );
           
            if ( override != null && override instanceof Long ){
             
                // this is to allow specific torrents to be refreshed more quickly
                // if the tracker permits. Parg
             
              min_interval_override = ((Long)override).longValue();
            }
          }

          if (complete_l != null || incomplete_l != null) {

            int complete = complete_l == null ? 0 : complete_l.intValue();

            int incomplete = incomplete_l == null ? 0 : incomplete_l.intValue();

            if (complete < 0 || incomplete < 0) {
              resp.setFailurReason(MessageText.getString(
                  "Tracker.announce.ignorePeerSeed",
                  new String[] { (complete < 0
                      ? MessageText.getString("MyTorrentsView.seeds") + " == "
                          + complete + ". " : "")
                      + (incomplete < 0
                          ? MessageText.getString("MyTorrentsView.peers")
                              + " == " + incomplete + ". " : "") }));
            } else {
             
              resp.setScrapeResult( complete, incomplete );

              TRTrackerScraper scraper = TRTrackerScraperFactory.getSingleton();

              if (scraper != null) {
                TRTrackerScraperResponse scrapeResponse = scraper.scrape( torrent, getTrackerURL());
                if (scrapeResponse != null) {
                  long lNextScrapeTime = scrapeResponse.getNextScrapeStartTime();
                  long lNewNextScrapeTime = TRTrackerScraperResponseImpl.calcScrapeIntervalSecs(
                      0, complete) * 1000;

                  // make it look as if the scrape has just run. Important
                  // as seeding rules may make calculations on when the
                  // scrape value were set

                  scrapeResponse.setScrapeStartTime(SystemTime.getCurrentTime());

                  if (lNextScrapeTime < lNewNextScrapeTime)
                    scrapeResponse.setNextScrapeStartTime(lNewNextScrapeTime);

                  scrapeResponse.setSeedsPeers(complete, incomplete);

                  if ( downloaded_l != null ){
                   
                    scrapeResponse.setCompleted( downloaded_l.intValue());
                  }
                }
              }
            }
          }

          return (resp)

        }catch( IOException e ){
         
            // decode could fail if the tracker's returned, say, an HTTP response
            // indicating server overload
                   
           String  trace_data;
          
           if ( data.length <= 150 ){
            
             trace_data = new String(data);
            
           }else{
            
             trace_data = new String(data,0,150) + "...";
           }
          
           if (Logger.isEnabled())
            Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
                "TRTrackerAnnouncer::invalid reply: " + trace_data));
          
           failure_reason = "invalid reply: " + trace_data;
         }          
       }catch( Throwable e ){
       
         Debug.printStackTrace( e );
       
        failure_reason = "error: " + e.getMessage();
      }
      }

    return( new TRTrackerAnnouncerResponseImpl( url, torrent_hash, TRTrackerAnnouncerResponse.ST_OFFLINE, getErrorRetryInterval(), failure_reason ));
    }
   
    private Long
    getLong(
      Map    map,
      String  key )
    {
      Object o = map.get( key );
     
      if ( o instanceof Long ){
       
        return((Long)o);
      }
     
      return( null );
    }
   
  protected void
  informURLChange(
    URL    old_url,
    URL    new_url,
    boolean  explicit  )
  {
    helper.informURLChange( old_url, new_url, explicit );
  }
 
  protected void
  informURLRefresh()
  {
    helper.informURLRefresh()
  }
 
  public TRTrackerAnnouncerResponse
  getLastResponse()
  {
    if( last_response == null ){
     
      return new TRTrackerAnnouncerResponseImpl( null, torrent_hash, TRTrackerAnnouncerResponse.ST_OFFLINE, TRTrackerAnnouncer.REFRESH_MINIMUM_SECS, "Initialising" );
    }
   
    return( last_response );
  }
 
  public boolean
  isManual()
  {
    return( manual_control );
  }
  public void
  destroy()
  {      
    destroyed  = true;
       
    try{
      this_mon.enter();
     
      if ( current_timer_event != null ){
       
          // cancel any events that are a way off being triggered. note that
          // we don't want to cancel all events as the "stopped" event that
          // is scheduled on stop of a download may still be lurking
       
        if ( current_timer_event.getWhen() - SystemTime.getCurrentTime() > 10*1000 ){
         
          if (Logger.isEnabled())
            Logger.log(new LogEvent(torrent, LOGID,
                "Canceling announce trigger"));

          current_timer_event.cancel();
        }
      }
    }finally{
     
      this_mon.exit();
    }
  }
 
 
  /**
   * Retrieve the retry interval to use on announce errors.
   */
  protected int getErrorRetryInterval() {
   
    long currentTime = SystemTime.getCurrentTime() /1000;
       
    long diff = currentTime - failure_time_last_updated;
   
    //use previously calculated interval if it's not time to update
    if( diff < failure_added_time && !(diff < 0) ) {
      return failure_added_time;
    }

    //update previous change time
    failure_time_last_updated = currentTime;
   
    if( failure_added_time == 0 ) { //start
      failure_added_time = 10;
    }
    else if( failure_added_time < 30 ) {
      //three 10-sec retries
      failure_added_time += 10;
    }
    else if( failure_added_time < 60 ) {
      //two 15-sec retries
      failure_added_time += 15;
    }
    else if( failure_added_time < 120 ) {
      //two 30-sec retries
      failure_added_time += 30;
    }
    else if( failure_added_time < 600 ) {
      //eight 60-sec retries
      failure_added_time += 60;
    }
    else {
      //2-3min random retry
      failure_added_time += 120 + new Random().nextInt( 60 );
    }

    boolean is_seed = (announce_data_provider == null) ? false : announce_data_provider.getRemaining() == 0;
   
    if( is_seed ) failure_added_time = failure_added_time * 2; //no need to retry as often
   
    //make sure we're not waiting longer than 30min
    if( !is_seed && failure_added_time > 1800) {
      failure_added_time = 1800;
    }
    else if ( is_seed && failure_added_time > 3600) { //or 60min if seed
      failure_added_time = 3600;
    }

    return failure_added_time;
  }
 
  public void
  setAnnounceResult(
    DownloadAnnounceResult  result )
  {
      // this is how the results from "external" announces get into the system
      // really should refactor so that "normal" and "external" mechanisms are
      // just instances of the same generic approach
   
    TRTrackerAnnouncerResponseImpl   response;
    String              status;
   
    if ( result.getResponseType() == DownloadAnnounceResult.RT_ERROR ){
     
      status = MessageText.getString("PeerManager.status.error");
         
      String  reason = result.getError();
 
      if ( reason != null ){
   
        status += " (" + reason + ")";   
      }
     
        response = new TRTrackerAnnouncerResponseImpl(
                  result.getURL(),
                  torrent_hash,
                  TRTrackerAnnouncerResponse.ST_OFFLINE,
                result.getTimeToWait(),
                reason );
    }else{
      DownloadAnnounceResultPeer[]  ext_peers = result.getPeers();
     
      List  l_peers = new ArrayList(ext_peers.length);
       
      boolean ps_enabled =   announce_data_provider != null &&
                  announce_data_provider.isPeerSourceEnabled( PEPeerSource.PS_BT_TRACKER );
           
      for (int i=0;i<ext_peers.length;i++){
       
        DownloadAnnounceResultPeer  ext_peer = ext_peers[i];
       
        String  ps = ext_peer.getSource();
       
          // filter out any disabled peers
       
        if ( !ps_enabled && ps.equals( PEPeerSource.PS_BT_TRACKER )){
                   
          continue;
         
        }else{
         
          int    http_port  = 0;
          byte  az_version   = TRTrackerAnnouncer.AZ_TRACKER_VERSION_1;
         
          TRTrackerAnnouncerResponsePeerImpl p =
            new TRTrackerAnnouncerResponsePeerImpl(
                  ext_peer.getSource(),
                  ext_peer.getPeerID(),
                  ext_peer.getAddress(),
                  ext_peer.getPort(),
                  ext_peer.getUDPPort(),
                  http_port,
                  ext_peer.getProtocol(),
                  az_version,
                  (short)0 );
         
          l_peers.add( p );
       
          if (Logger.isEnabled())
            Logger.log(new LogEvent(torrent, LOGID, "EXTERNAL PEER: " + p.getString()));
        }
      }
     
      TRTrackerAnnouncerResponsePeerImpl[] peers = new TRTrackerAnnouncerResponsePeerImpl[ l_peers.size()];

      l_peers.toArray( peers );
     
      helper.addToTrackerCache( peers);
   
      if ( ps_enabled || peers.length > 0 || ext_peers.length == 0 ){
       
        status = MessageText.getString("PeerManager.status.ok");

      }else{
       
        status = MessageText.getString("PeerManager.status.ps_disabled");
       
        peers = new TRTrackerAnnouncerResponsePeerImpl[0];
      }
     
      response =
        new TRTrackerAnnouncerResponseImpl(
            result.getURL(),
            torrent_hash,
            TRTrackerAnnouncerResponse.ST_ONLINE,
            result.getTimeToWait(),
            peers );
    }
   
      // only make the user aware of the status if the underlying announce is
      // failing
   
    if (   last_response == null ||
        last_response.getStatus() != TRTrackerAnnouncerResponse.ST_ONLINE ){
     
      tracker_status_str  = status + " (" + result.getURL() + ")";
    }
   
    helper.informResponse( this, response );
  }
 
  public void
  addListener(
    TRTrackerAnnouncerListener l )
  {
    helper.addListener( l );
  }
 
  public void
  removeListener(
    TRTrackerAnnouncerListener l )
  {
    helper.removeListener( l );
  }
 
  public void
  setTrackerResponseCache(
    Map map  )
  {
    helper.setTrackerResponseCache( map );
  }
 
  public void
  removeFromTrackerResponseCache(
    String ip, int tcpPort)
  {
    helper.removeFromTrackerResponseCache( ip, tcpPort );
  }
 
  public Map
  getTrackerResponseCache()
  {
    return( helper.getTrackerResponseCache());
  }
 
  public TrackerPeerSource
  getTrackerPeerSource(
    TOTorrentAnnounceURLSet set)
  {
    Debug.out( "not implemented" );
   
    return null;
  }
 
  public TrackerPeerSource
  getCacheTrackerPeerSource()
  {
    Debug.out( "not implemented" );
   
    return null;
  }
 
  public void
  generateEvidence(
    IndentWriter writer )
  {
    writer.println( "BT announce:" );
   
    try{
      writer.indent();
     
      writer.println( "state: " + tracker_state + ", in_progress=" + update_in_progress );
     
      writer.println( "current: " + (lastUsedUrl==null?"null":lastUsedUrl.toString()));
     
      writer.println( "last: " + (last_response==null?"null":last_response.getString()));
     
      writer.println( "last_update_secs: " + last_update_time_secs );
     
      writer.println( "secs_to_wait: " + current_time_to_wait_secs  + (manual_control?" - manual":""));
     
      writer.println( "t_interval: " + tracker_interval );
     
      writer.println( "t_min_interval: " + tracker_min_interval );
     
      writer.println( "min_interval: " + min_interval );
     
      writer.println( "min_interval_override: " + min_interval_override );
     
      writer.println( "rd: last_override=" + rd_last_override + ",percentage=" + rd_override_percentage );
     
      writer.println( "event: " + ( current_timer_event==null?"null":current_timer_event.getString()));
     
      writer.println( "stopped: " + stopped + ", for_q=" + stopped_for_queue );
     
      writer.println( "complete: " + completed + ", reported=" + complete_reported );
     
    }finally{
      writer.exdent();
    }
  }
}
TOP

Related Classes of org.gudy.azureus2.core3.tracker.client.impl.bt.TRTrackerBTAnnouncerImpl

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.