/*
* Created by Olivier Chalouhi
* Modified Apr 13, 2004 by Alon Rohter
* Heavily modified Sep 2005 by Joseph Bridgewater
* Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*/
package org.gudy.azureus2.core3.peer.impl.control;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.*;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.disk.*;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.ipfilter.*;
import org.gudy.azureus2.core3.logging.*;
import org.gudy.azureus2.core3.peer.*;
import org.gudy.azureus2.core3.peer.impl.*;
import org.gudy.azureus2.core3.peer.util.PeerIdentityDataID;
import org.gudy.azureus2.core3.peer.util.PeerIdentityManager;
import org.gudy.azureus2.core3.peer.util.PeerUtils;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponse;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponsePeer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerScraperResponse;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.plugins.download.DownloadAnnounceResultPeer;
import org.gudy.azureus2.plugins.network.Connection;
import org.gudy.azureus2.plugins.network.OutgoingMessageQueue;
import org.gudy.azureus2.plugins.peers.Peer;
import org.gudy.azureus2.plugins.peers.PeerDescriptor;
import com.aelitis.azureus.core.networkmanager.LimitedRateGroup;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin;
import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPConnectionManager;
import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPNetworkManager;
import com.aelitis.azureus.core.networkmanager.impl.udp.UDPNetworkManager;
import com.aelitis.azureus.core.peermanager.control.PeerControlInstance;
import com.aelitis.azureus.core.peermanager.control.PeerControlScheduler;
import com.aelitis.azureus.core.peermanager.control.PeerControlSchedulerFactory;
import com.aelitis.azureus.core.peermanager.nat.PeerNATInitiator;
import com.aelitis.azureus.core.peermanager.nat.PeerNATTraversalAdapter;
import com.aelitis.azureus.core.peermanager.nat.PeerNATTraverser;
import com.aelitis.azureus.core.peermanager.peerdb.*;
import com.aelitis.azureus.core.peermanager.piecepicker.PiecePicker;
import com.aelitis.azureus.core.peermanager.piecepicker.PiecePickerFactory;
import com.aelitis.azureus.core.peermanager.unchoker.Unchoker;
import com.aelitis.azureus.core.peermanager.unchoker.UnchokerFactory;
import com.aelitis.azureus.core.peermanager.unchoker.UnchokerUtil;
import com.aelitis.azureus.core.peermanager.uploadslots.UploadHelper;
import com.aelitis.azureus.core.peermanager.uploadslots.UploadSlotManager;
import com.aelitis.azureus.core.tracker.TrackerPeerSource;
import com.aelitis.azureus.core.tracker.TrackerPeerSourceAdapter;
import com.aelitis.azureus.core.util.FeatureAvailability;
import com.aelitis.azureus.core.util.bloom.BloomFilter;
import com.aelitis.azureus.core.util.bloom.BloomFilterFactory;
/**
* manages all peer transports for a torrent
*
* @author MjrTom
* 2005/Oct/08: Numerous changes for new piece-picking. Also
* a few optimizations and multi-thread cleanups
* 2006/Jan/02: refactoring piece picking related code
*/
@SuppressWarnings("serial")
public class
PEPeerControlImpl
extends LogRelation
implements PEPeerControl, ParameterListener, DiskManagerWriteRequestListener, PeerControlInstance, PeerNATInitiator,
DiskManagerCheckRequestListener, IPFilterListener
{
private static final LogIDs LOGID = LogIDs.PEER;
private static final boolean TEST_PERIODIC_SEEDING_SCAN_FAIL_HANDLING = false;
static{
if ( TEST_PERIODIC_SEEDING_SCAN_FAIL_HANDLING ){
Debug.out( "**** test periodic scan failure enabled ****" );
}
}
private static final int WARNINGS_LIMIT = 2;
private static final int CHECK_REASON_DOWNLOADED = 1;
private static final int CHECK_REASON_COMPLETE = 2;
private static final int CHECK_REASON_SCAN = 3;
private static final int CHECK_REASON_SEEDING_CHECK = 4;
private static final int CHECK_REASON_BAD_PIECE_CHECK = 5;
private static final int SEED_CHECK_WAIT_MARKER = 65526;
// config
private static boolean disconnect_seeds_when_seeding;
private static boolean enable_seeding_piece_rechecks;
private static int stalled_piece_timeout;
private static boolean fast_unchoke_new_peers;
private static float ban_peer_discard_ratio;
private static int ban_peer_discard_min_kb;
private static boolean udp_fallback_for_failed_connection;
private static boolean udp_fallback_for_dropped_connection;
private static boolean udp_probe_enabled;
private static boolean hide_a_piece;
private static boolean prefer_udp_default;
static{
COConfigurationManager.addAndFireParameterListeners(
new String[]{
"Disconnect Seed",
"Seeding Piece Check Recheck Enable",
"peercontrol.stalled.piece.write.timeout",
"Peer.Fast.Initial.Unchoke.Enabled",
"Ip Filter Ban Discard Ratio",
"Ip Filter Ban Discard Min KB",
"peercontrol.udp.fallback.connect.fail",
"peercontrol.udp.fallback.connect.drop",
"peercontrol.udp.probe.enable",
"peercontrol.hide.piece",
"peercontrol.prefer.udp",
},
new ParameterListener()
{
public void
parameterChanged(
String name )
{
disconnect_seeds_when_seeding = COConfigurationManager.getBooleanParameter("Disconnect Seed");
enable_seeding_piece_rechecks = COConfigurationManager.getBooleanParameter("Seeding Piece Check Recheck Enable");
stalled_piece_timeout = COConfigurationManager.getIntParameter( "peercontrol.stalled.piece.write.timeout", 60*1000 );
fast_unchoke_new_peers = COConfigurationManager.getBooleanParameter( "Peer.Fast.Initial.Unchoke.Enabled" );
ban_peer_discard_ratio = COConfigurationManager.getFloatParameter( "Ip Filter Ban Discard Ratio" );
ban_peer_discard_min_kb = COConfigurationManager.getIntParameter( "Ip Filter Ban Discard Min KB" );
udp_fallback_for_failed_connection = COConfigurationManager.getBooleanParameter( "peercontrol.udp.fallback.connect.fail" );
udp_fallback_for_dropped_connection = COConfigurationManager.getBooleanParameter( "peercontrol.udp.fallback.connect.drop" );
udp_probe_enabled = COConfigurationManager.getBooleanParameter( "peercontrol.udp.probe.enable" );
hide_a_piece = COConfigurationManager.getBooleanParameter( "peercontrol.hide.piece" );
if ( hide_a_piece ){
disconnect_seeds_when_seeding = false;
}
prefer_udp_default = COConfigurationManager.getBooleanParameter( "peercontrol.prefer.udp" );
}
});
}
private static IpFilter ip_filter = IpFilterManagerFactory.getSingleton().getIPFilter();
private volatile boolean is_running = false;
private volatile boolean is_destroyed = false;
private volatile ArrayList<PEPeer> peer_transports_cow = new ArrayList<PEPeer>(); // Copy on write!
private final AEMonitor peer_transports_mon = new AEMonitor( "PEPeerControl:PT");
protected final PEPeerManagerAdapter adapter;
private final DiskManager disk_mgr;
private final DiskManagerPiece[] dm_pieces;
private PEPeerManager.StatsReceiver stats_receiver;
private final PiecePicker piecePicker;
private long lastNeededUndonePieceChange;
/** literally seeding as in 100% torrent complete */
private boolean seeding_mode;
private boolean restart_initiated;
private final int _nbPieces; //how many pieces in the torrent
private PEPieceImpl[] pePieces; //pieces that are currently in progress
private int nbPiecesActive; // how many pieces are currently in progress
private int nbPeersSnubbed;
private PeerIdentityDataID _hash;
private final byte[] _myPeerId;
private PEPeerManagerStats _stats;
//private final TRTrackerAnnouncer _tracker;
// private int _maxUploads;
private int _seeds, _peers,_remotesTCPNoLan, _remotesUDPNoLan;
private int _tcpPendingConnections, _tcpConnectingConnections;
private long last_remote_time;
private long _timeStarted;
private long _timeStartedSeeding = -1;
private long _timeFinished;
private Average _averageReceptionSpeed;
private long mainloop_loop_count;
private static final int MAINLOOP_ONE_SECOND_INTERVAL = 1000 / PeerControlScheduler.SCHEDULE_PERIOD_MILLIS;
private static final int MAINLOOP_FIVE_SECOND_INTERVAL = MAINLOOP_ONE_SECOND_INTERVAL * 5;
private static final int MAINLOOP_TEN_SECOND_INTERVAL = MAINLOOP_ONE_SECOND_INTERVAL * 10;
private static final int MAINLOOP_THIRTY_SECOND_INTERVAL = MAINLOOP_ONE_SECOND_INTERVAL * 30;
private static final int MAINLOOP_SIXTY_SECOND_INTERVAL = MAINLOOP_ONE_SECOND_INTERVAL * 60;
private static final int MAINLOOP_TEN_MINUTE_INTERVAL = MAINLOOP_SIXTY_SECOND_INTERVAL * 10;
private volatile ArrayList<PEPeerManagerListener> peer_manager_listeners_cow = new ArrayList<PEPeerManagerListener>(); //copy on write
private final List<Object[]> piece_check_result_list = new ArrayList<Object[]>();
private final AEMonitor piece_check_result_list_mon = new AEMonitor( "PEPeerControl:PCRL");
private boolean superSeedMode;
private int superSeedModeCurrentPiece;
private int superSeedModeNumberOfAnnounces;
private SuperSeedPiece[] superSeedPieces;
private final int hidden_piece;
private final AEMonitor this_mon = new AEMonitor( "PEPeerControl");
private long ip_filter_last_update_time;
private Map<Object,Object> user_data;
private Unchoker unchoker;
private List<Object[]> external_rate_limiters_cow;
private int bytes_queued_for_upload;
private int connections_with_queued_data;
private int connections_with_queued_data_blocked;
private int connections_unchoked;
private List<PEPeer> sweepList = Collections.emptyList();
private int nextPEXSweepIndex = 0;
private final UploadHelper upload_helper = new UploadHelper() {
public int getPriority() {
return UploadHelper.PRIORITY_NORMAL; //TODO also must call UploadSlotManager.getSingleton().updateHelper( upload_helper ); on priority change
}
public ArrayList<PEPeer> getAllPeers() {
return( peer_transports_cow );
}
public boolean isSeeding() {
return seeding_mode;
}
};
private PeerDatabase peer_database = PeerDatabaseFactory.createPeerDatabase();
private int bad_piece_reported = -1;
private int next_rescan_piece = -1;
private long rescan_piece_time = -1;
private long last_eta;
private long last_eta_calculation;
private static final int MAX_UDP_CONNECTIONS = 16;
private static final int PENDING_NAT_TRAVERSAL_MAX = 32;
private static final int MAX_UDP_TRAVERSAL_COUNT = 3;
private static final String PEER_NAT_TRAVERSE_DONE_KEY = PEPeerControlImpl.class.getName() + "::nat_trav_done";
private Map<String,PEPeerTransport> pending_nat_traversals =
new LinkedHashMap<String,PEPeerTransport>(PENDING_NAT_TRAVERSAL_MAX,0.75f,true)
{
protected boolean
removeEldestEntry(
Map.Entry<String,PEPeerTransport> eldest)
{
return size() > PENDING_NAT_TRAVERSAL_MAX;
}
};
private int udp_traversal_count;
private static final int UDP_RECONNECT_MAX = 16;
private Map<String,PEPeerTransport> udp_reconnects =
new LinkedHashMap<String,PEPeerTransport>(UDP_RECONNECT_MAX,0.75f,true)
{
protected boolean
removeEldestEntry(
Map.Entry<String,PEPeerTransport> eldest)
{
return size() > UDP_RECONNECT_MAX;
}
};
private static final int UDP_RECONNECT_MIN_MILLIS = 10*1000;
private long last_udp_reconnect;
private boolean prefer_udp;
private static final int PREFER_UDP_BLOOM_SIZE = 10000;
private volatile BloomFilter prefer_udp_bloom;
private final LimitedRateGroup upload_limited_rate_group = new LimitedRateGroup() {
public String
getName()
{
return( "per_dl_up: " + getDisplayName());
}
public int getRateLimitBytesPerSecond() {
return adapter.getUploadRateLimitBytesPerSecond();
}
};
private final LimitedRateGroup download_limited_rate_group = new LimitedRateGroup() {
public String
getName()
{
return( "per_dl_down: " + getDisplayName());
}
public int getRateLimitBytesPerSecond() {
return adapter.getDownloadRateLimitBytesPerSecond();
}
};
private final int partition_id;
public
PEPeerControlImpl(
byte[] _peer_id,
PEPeerManagerAdapter _adapter,
DiskManager _diskManager,
int _partition_id )
{
_myPeerId = _peer_id;
adapter = _adapter;
disk_mgr = _diskManager;
partition_id = _partition_id;
_nbPieces =disk_mgr.getNbPieces();
dm_pieces =disk_mgr.getPieces();
pePieces = new PEPieceImpl[_nbPieces];
hidden_piece = hide_a_piece?((int)(Math.abs(adapter.getRandomSeed())%_nbPieces)):-1;
if ( hidden_piece >= 0 ){
System.out.println( "Hidden piece for " + getDisplayName() + " = " + hidden_piece );
}
piecePicker = PiecePickerFactory.create( this );
COConfigurationManager.addParameterListener("Ip Filter Enabled", this);
ip_filter.addListener( this );
}
public void
start()
{
//This torrent Hash
try
{
_hash = PeerIdentityManager.createDataID( disk_mgr.getTorrent().getHash());
} catch (TOTorrentException e)
{
// this should never happen
Debug.printStackTrace( e );
_hash = PeerIdentityManager.createDataID( new byte[20] );
}
// the recovered active pieces
for (int i =0; i <_nbPieces; i++ )
{
final DiskManagerPiece dmPiece =dm_pieces[i];
if (!dmPiece.isDone() &&dmPiece.getNbWritten() >0)
{
addPiece(new PEPieceImpl(this, dmPiece, 0), i, true, null );
}
}
//The peer connections
peer_transports_cow = new ArrayList();
//BtManager is threaded, this variable represents the
// current loop iteration. It's used by some components only called
// at some specific times.
mainloop_loop_count = 0;
//The current tracker state
//this could be start or update
_averageReceptionSpeed = Average.getInstance(1000, 30);
// the stats
_stats =new PEPeerManagerStatsImpl(this);
superSeedMode = (COConfigurationManager.getBooleanParameter("Use Super Seeding") && this.getRemaining() == 0);
superSeedModeCurrentPiece = 0;
if (superSeedMode)
{
initialiseSuperSeedMode();
}
// initial check on finished state - future checks are driven by piece check results
// Moved out of mainLoop() so that it runs immediately, possibly changing
// the state to seeding.
checkFinished( true );
UploadSlotManager.getSingleton().registerHelper( upload_helper );
lastNeededUndonePieceChange =Long.MIN_VALUE;
_timeStarted = SystemTime.getCurrentTime();
is_running = true;
// activate after marked as running as we may synchronously add connections here due to pending activations
adapter.getPeerManagerRegistration().activate( this );
PeerNATTraverser.getSingleton().register( this );
PeerControlSchedulerFactory.getSingleton(partition_id).register(this);
}
public void stopAll()
{
is_running =false;
UploadSlotManager.getSingleton().deregisterHelper( upload_helper );
PeerControlSchedulerFactory.getSingleton(partition_id).unregister(this);
PeerNATTraverser.getSingleton().unregister( this );
// remove legacy controller activation
adapter.getPeerManagerRegistration().deactivate();
closeAndRemoveAllPeers("download stopped", false);
// clear pieces
for (int i =0; i <_nbPieces; i++ )
{
if (pePieces[i] !=null)
removePiece(pePieces[i], i);
}
// 5. Remove listeners
COConfigurationManager.removeParameterListener("Ip Filter Enabled", this);
ip_filter.removeListener(this);
piecePicker.destroy();
final ArrayList<PEPeerManagerListener> peer_manager_listeners = peer_manager_listeners_cow;
for( int i=0; i < peer_manager_listeners.size(); i++ ) {
((PEPeerManagerListener)peer_manager_listeners.get(i)).destroyed();
}
sweepList = Collections.emptyList();
pending_nat_traversals.clear();
udp_reconnects.clear();
is_destroyed = true;
}
public int
getPartitionID()
{
return( partition_id );
}
public boolean
isDestroyed()
{
return( is_destroyed );
}
public DiskManager getDiskManager() { return disk_mgr; }
public PiecePicker getPiecePicker()
{
return piecePicker;
}
public PEPeerManagerAdapter getAdapter(){ return( adapter ); }
public String getDisplayName(){ return( adapter.getDisplayName()); }
public String
getName()
{
return( getDisplayName());
}
public void
schedule()
{
try{
// first off update the stats so they can be used by subsequent steps
updateStats();
updateTrackerAnnounceInterval();
doConnectionChecks();
processPieceChecks();
// note that seeding_mode -> torrent totally downloaded, not just non-dnd files
// complete, so there is no change of a new piece appearing done by a means such as
// background periodic file rescans
if ( !seeding_mode ){
checkCompletedPieces(); //check to see if we've completed anything else
}
checkBadPieces();
checkInterested(); // see if need to recheck Interested on all peers
piecePicker.updateAvailability();
checkCompletionState(); // pick up changes in completion caused by dnd file changes
checkSeeds();
if(!seeding_mode) {
// if we're not finished
checkRequests();
piecePicker.allocateRequests();
checkRescan();
checkSpeedAndReserved();
check99PercentBug();
}
updatePeersInSuperSeedMode();
doUnchokes();
}catch (Throwable e) {
Debug.printStackTrace( e );
}
mainloop_loop_count++;
}
/**
* A private method that does analysis of the result sent by the tracker.
* It will mainly open new connections with peers provided
* and set the timeToWait variable according to the tracker response.
* @param tracker_response
*/
private void
analyseTrackerResponse(
TRTrackerAnnouncerResponse tracker_response )
{
// tracker_response.print();
final TRTrackerAnnouncerResponsePeer[] peers = tracker_response.getPeers();
if ( peers != null ){
addPeersFromTracker( tracker_response.getPeers());
}
final Map extensions = tracker_response.getExtensions();
if (extensions != null ){
addExtendedPeersFromTracker( extensions );
}
}
public void
processTrackerResponse(
TRTrackerAnnouncerResponse response )
{
// only process new peers if we're still running
if ( is_running ){
analyseTrackerResponse( response );
}
}
private void
addExtendedPeersFromTracker(
Map extensions )
{
final Map protocols = (Map)extensions.get("protocols");
if ( protocols != null ){
System.out.println( "PEPeerControl: tracker response contained protocol extensions");
final Iterator protocol_it = protocols.keySet().iterator();
while( protocol_it.hasNext()){
final String protocol_name = (String)protocol_it.next();
final Map protocol = (Map)protocols.get(protocol_name);
final List transports = PEPeerTransportFactory.createExtendedTransports( this, protocol_name, protocol );
for (int i=0;i<transports.size();i++){
final PEPeer transport = (PEPeer)transports.get(i);
addPeer( transport );
}
}
}
}
public List<PEPeer>
getPeers()
{
return( peer_transports_cow );
}
public List<PEPeer>
getPeers(
String address )
{
List<PEPeer> result = new ArrayList<PEPeer>();
Iterator<PEPeer> it = peer_transports_cow.iterator();
while( it.hasNext()){
PEPeerTransport peer = (PEPeerTransport)it.next();
if ( peer.getIp().equals( address )){
result.add( peer );
}
}
return( result );
}
public int
getPendingPeerCount()
{
return( peer_database.getDiscoveredPeerCount());
}
public PeerDescriptor[]
getPendingPeers()
{
return((PeerDescriptor[])peer_database.getDiscoveredPeers());
}
public PeerDescriptor[]
getPendingPeers(
String address )
{
return((PeerDescriptor[])peer_database.getDiscoveredPeers( address ));
}
public void
addPeer(
PEPeer _transport )
{
if ( !( _transport instanceof PEPeerTransport )){
throw( new RuntimeException("invalid class"));
}
final PEPeerTransport transport = (PEPeerTransport)_transport;
if (!ip_filter.isInRange(transport.getIp(), getDisplayName(), getTorrentHash())) {
final ArrayList peer_transports = peer_transports_cow;
if ( !peer_transports.contains(transport)){
addToPeerTransports( transport );
transport.start();
}else{
Debug.out( "addPeer():: peer_transports.contains(transport): SHOULD NEVER HAPPEN !" );
transport.closeConnection( "already connected" );
}
}else{
transport.closeConnection( "IP address blocked by filters" );
}
}
protected byte[]
getTorrentHash()
{
try{
return( disk_mgr.getTorrent().getHash());
}catch( Throwable e ){
return( null );
}
}
public void
removePeer(
PEPeer _transport )
{
removePeer( _transport, "remove peer" );
}
public void
removePeer(
PEPeer _transport,
String reason )
{
if ( !( _transport instanceof PEPeerTransport )){
throw( new RuntimeException("invalid class"));
}
PEPeerTransport transport = (PEPeerTransport)_transport;
closeAndRemovePeer( transport, reason, true );
}
private void closeAndRemovePeer( PEPeerTransport peer, String reason, boolean log_if_not_found )
{
boolean removed =false;
// copy-on-write semantics
try{
peer_transports_mon.enter();
if ( peer_transports_cow.contains( peer )){
final ArrayList new_peer_transports = new ArrayList( peer_transports_cow );
new_peer_transports.remove(peer);
peer_transports_cow =new_peer_transports;
removed =true;
}
}
finally{
peer_transports_mon.exit();
}
if( removed ) {
peer.closeConnection( reason );
peerRemoved(peer); //notify listeners
}
else {
if ( log_if_not_found ){
// we know this happens due to timing issues... Debug.out( "closeAndRemovePeer(): peer not removed" );
}
}
}
private void closeAndRemoveAllPeers( String reason, boolean reconnect ) {
ArrayList peer_transports;
try{
peer_transports_mon.enter();
peer_transports = peer_transports_cow;
peer_transports_cow = new ArrayList( 0 );
}
finally{
peer_transports_mon.exit();
}
for( int i=0; i < peer_transports.size(); i++ ) {
final PEPeerTransport peer = (PEPeerTransport)peer_transports.get( i );
try{
peer.closeConnection( reason );
}catch( Throwable e ){
// if something goes wrong with the close process (there's a bug in there somewhere whereby
// we occasionally get NPEs then we want to make sure we carry on and close the rest
Debug.printStackTrace(e);
}
try{
peerRemoved( peer ); //notify listeners
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
if( reconnect ) {
for( int i=0; i < peer_transports.size(); i++ ) {
final PEPeerTransport peer = (PEPeerTransport)peer_transports.get( i );
PEPeerTransport reconnected_peer = peer.reconnect(false, false);
}
}
}
public void
addPeer(
String ip_address,
int tcp_port,
int udp_port,
boolean use_crypto,
Map user_data )
{
final byte type = use_crypto ? PeerItemFactory.HANDSHAKE_TYPE_CRYPTO : PeerItemFactory.HANDSHAKE_TYPE_PLAIN;
final PeerItem peer_item = PeerItemFactory.createPeerItem( ip_address, tcp_port, PeerItem.convertSourceID( PEPeerSource.PS_PLUGIN ), type, udp_port, PeerItemFactory.CRYPTO_LEVEL_1, 0 );
byte crypto_level = PeerItemFactory.CRYPTO_LEVEL_1;
if( !isAlreadyConnected( peer_item ) ) {
String fail_reason;
boolean tcp_ok = TCPNetworkManager.TCP_OUTGOING_ENABLED && tcp_port > 0;
boolean udp_ok = UDPNetworkManager.UDP_OUTGOING_ENABLED && udp_port > 0;
if ( tcp_ok && !( ( prefer_udp || prefer_udp_default ) && udp_ok )){
fail_reason = makeNewOutgoingConnection( PEPeerSource.PS_PLUGIN, ip_address, tcp_port, udp_port, true, use_crypto, crypto_level, user_data ); //directly inject the the imported peer
}else if ( udp_ok ){
fail_reason = makeNewOutgoingConnection( PEPeerSource.PS_PLUGIN, ip_address, tcp_port, udp_port, false, use_crypto, crypto_level, user_data ); //directly inject the the imported peer
}else{
fail_reason = "No usable protocol";
}
if( fail_reason != null ) Debug.out( "Injected peer " + ip_address + ":" + tcp_port + " was not added - " + fail_reason );
}
}
private void
addPeersFromTracker(
TRTrackerAnnouncerResponsePeer[] peers )
{
for (int i = 0; i < peers.length; i++){
final TRTrackerAnnouncerResponsePeer peer = peers[i];
final ArrayList peer_transports = peer_transports_cow;
boolean already_connected = false;
for( int x=0; x < peer_transports.size(); x++ ) {
final PEPeerTransport transport = (PEPeerTransport)peer_transports.get( x );
// allow loopback connects for co-located proxy-based connections and testing
if( peer.getAddress().equals( transport.getIp() )){
final boolean same_allowed = COConfigurationManager.getBooleanParameter( "Allow Same IP Peers" ) ||
transport.getIp().equals( "127.0.0.1" );
if( !same_allowed || peer.getPort() == transport.getPort() ) {
already_connected = true;
break;
}
}
}
if( already_connected ) continue;
if( peer_database != null ){
byte type = peer.getProtocol() == DownloadAnnounceResultPeer.PROTOCOL_CRYPT ? PeerItemFactory.HANDSHAKE_TYPE_CRYPTO : PeerItemFactory.HANDSHAKE_TYPE_PLAIN;
byte crypto_level = peer.getAZVersion() < TRTrackerAnnouncer.AZ_TRACKER_VERSION_3?PeerItemFactory.CRYPTO_LEVEL_1:PeerItemFactory.CRYPTO_LEVEL_2;
PeerItem item = PeerItemFactory.createPeerItem(
peer.getAddress(),
peer.getPort(),
PeerItem.convertSourceID( peer.getSource() ),
type,
peer.getUDPPort(),
crypto_level,
peer.getUploadSpeed());
peerDiscovered( null, item );
peer_database.addDiscoveredPeer( item );
}
int http_port = peer.getHTTPPort();
if ( http_port != 0 && !seeding_mode ){
adapter.addHTTPSeed( peer.getAddress(), http_port );
}
}
}
/**
* Request a new outgoing peer connection.
* @param address ip of remote peer
* @param port remote peer listen port
* @return null if the connection was added to the transport list, reason if rejected
*/
private String
makeNewOutgoingConnection(
String peer_source,
String address,
int tcp_port,
int udp_port,
boolean use_tcp,
boolean require_crypto,
byte crypto_level,
Map user_data )
{
//make sure this connection isn't filtered
if( ip_filter.isInRange( address, getDisplayName(), getTorrentHash())) {
return "IPFilter block";
}
//make sure we need a new connection
final int needed = getMaxNewConnectionsAllowed();
boolean is_priority_connection = false;
if ( user_data != null ){
Boolean pc = (Boolean)user_data.get( Peer.PR_PRIORITY_CONNECTION );
if ( pc != null && pc.booleanValue()){
is_priority_connection = true;
}
}
if( needed == 0 ){
if ( peer_source != PEPeerSource.PS_PLUGIN ||
!doOptimisticDisconnect(
AddressUtils.isLANLocalAddress( address ) != AddressUtils.LAN_LOCAL_NO,
is_priority_connection )){
return "Too many connections";
}
}
//make sure not already connected to the same IP address; allow loopback connects for co-located proxy-based connections and testing
final boolean same_allowed = COConfigurationManager.getBooleanParameter( "Allow Same IP Peers" ) || address.equals( "127.0.0.1" );
if( !same_allowed && PeerIdentityManager.containsIPAddress( _hash, address ) ){
return "Already connected to IP";
}
if( PeerUtils.ignorePeerPort( tcp_port ) ) {
if (Logger.isEnabled())
Logger.log(new LogEvent(disk_mgr.getTorrent(), LOGID,
"Skipping connect with " + address + ":" + tcp_port
+ " as peer port is in ignore list."));
return "TCP port in ignore list";
}
//start the connection
PEPeerTransport real = PEPeerTransportFactory.createTransport( this, peer_source, address, tcp_port, udp_port, use_tcp, require_crypto, crypto_level, user_data );
addToPeerTransports( real );
return null;
}
/**
* A private method that checks if PEPieces being downloaded are finished
* If all blocks from a PEPiece are written to disk, this method will
* queue the piece for hash check.
* Elsewhere, if it passes sha-1 check, it will be marked as downloaded,
* otherwise, it will unmark it as fully downloaded, so blocks can be retreived again.
*/
private void checkCompletedPieces() {
if ((mainloop_loop_count %MAINLOOP_ONE_SECOND_INTERVAL) !=0)
return;
//for every piece
for (int i = 0; i <_nbPieces; i++) {
final DiskManagerPiece dmPiece =dm_pieces[i];
//if piece is completly written, not already checking, and not Done
if (dmPiece.isNeedsCheck())
{
//check the piece from the disk
dmPiece.setChecking();
DiskManagerCheckRequest req =
disk_mgr.createCheckRequest(
i, new Integer(CHECK_REASON_DOWNLOADED));
req.setAdHoc( false );
disk_mgr.enqueueCheckRequest( req, this );
}
}
}
/** Checks given piece to see if it's active but empty, and if so deactivates it.
* @param pieceNumber to check
* @return true if the piece was removed and is no longer active (pePiece ==null)
*/
private boolean
checkEmptyPiece(
final int pieceNumber )
{
if ( piecePicker.isInEndGameMode()){
return false; // be sure to not remove pieces in EGM
}
final PEPiece pePiece =pePieces[pieceNumber];
final DiskManagerPiece dmPiece =dm_pieces[pieceNumber];
if ( pePiece == null || pePiece.isRequested())
return false;
if (dmPiece.getNbWritten() >0 ||pePiece.getNbUnrequested() < pePiece.getNbBlocks() ||pePiece.getReservedBy() !=null)
return false;
// reset in case dmpiece is in some skanky state
pePiece.reset();
removePiece(pePiece, pieceNumber);
return true;
}
/**
* Check if a piece's Speed is too fast for it to be getting new data
* and if a reserved pieced failed to get data within 120 seconds
*/
private void checkSpeedAndReserved()
{
// only check every 5 seconds
if(mainloop_loop_count % MAINLOOP_FIVE_SECOND_INTERVAL != 0)
return;
final int nbPieces =_nbPieces;
final PEPieceImpl[] pieces =pePieces;
//for every piece
for (int i =0; i <nbPieces; i++)
{
// placed before null-check in case it really removes a piece
checkEmptyPiece(i);
final PEPieceImpl pePiece =pieces[i];
// these checks are only against pieces being downloaded
// yet needing requests still/again
if (pePiece !=null)
{
final long timeSinceActivity =pePiece.getTimeSinceLastActivity()/1000;
int pieceSpeed =pePiece.getSpeed();
// block write speed slower than piece speed
if (pieceSpeed > 0 && timeSinceActivity*pieceSpeed*0.25 > DiskManager.BLOCK_SIZE/1024)
{
if(pePiece.getNbUnrequested() > 2)
pePiece.setSpeed(pieceSpeed-1);
else
pePiece.setSpeed(0);
}
if(timeSinceActivity > 120)
{
pePiece.setSpeed(0);
// has reserved piece gone stagnant?
final String reservingPeer =pePiece.getReservedBy();
if(reservingPeer !=null)
{
final PEPeerTransport pt = getTransportFromAddress(reservingPeer);
// Peer is too slow; Ban them and unallocate the piece
// but, banning is no good for peer types that get pieces reserved
// to them for other reasons, such as special seed peers
if (needsMD5CheckOnCompletion(i))
badPeerDetected(reservingPeer, i);
else if (pt != null)
closeAndRemovePeer(pt, "Reserved piece data timeout; 120 seconds", true);
pePiece.setReservedBy(null);
}
if (!piecePicker.isInEndGameMode()){
pePiece.checkRequests();
}
checkEmptyPiece(i);
}
}
}
}
private void
check99PercentBug()
{
// there's a bug whereby pieces are left downloaded but never written. might have been fixed by
// changes to the "write result" logic, however as a stop gap I'm adding code to scan for such
// stuck pieces and reset them
if ( mainloop_loop_count % MAINLOOP_SIXTY_SECOND_INTERVAL == 0 ) {
long now = SystemTime.getCurrentTime();
for ( int i=0;i<pePieces.length;i++){
PEPiece pe_piece = pePieces[ i ];
if ( pe_piece != null ){
DiskManagerPiece dm_piece = dm_pieces[i];
if ( !dm_piece.isDone()){
if ( pe_piece.isDownloaded()){
if ( now - pe_piece.getLastDownloadTime(now) > stalled_piece_timeout ){
// people with *very* slow disk writes can trigger this (I've been talking to a user
// with a SAN that has .5 second write latencies when checking a file at the same time
// this means that when dowloading > 32K/sec things start backing up). Eventually the
// write controller will start blocking the network thread to prevent unlimited
// queueing but until that time we need to handle this situation slightly better)
// if there are any outstanding requests for this piece then leave it alone
if ( !( disk_mgr.hasOutstandingWriteRequestForPiece( i ) ||
disk_mgr.hasOutstandingReadRequestForPiece( i ) ||
disk_mgr.hasOutstandingCheckRequestForPiece( i ) )){
Debug.out( "Fully downloaded piece stalled pending write, resetting p_piece " + i );
pe_piece.reset();
}
}
}
}
}
}
}
}
private void checkInterested()
{
if ( (mainloop_loop_count %MAINLOOP_ONE_SECOND_INTERVAL) != 0 ){
return;
}
if (lastNeededUndonePieceChange >=piecePicker.getNeededUndonePieceChange())
return;
lastNeededUndonePieceChange =piecePicker.getNeededUndonePieceChange();
final ArrayList peer_transports = peer_transports_cow;
int cntPeersSnubbed =0; // recount # snubbed peers while we're at it
for (int i =0; i <peer_transports.size(); i++)
{
final PEPeerTransport peer =(PEPeerTransport)peer_transports.get(i);
peer.checkInterested();
if (peer.isSnubbed())
cntPeersSnubbed++;
}
setNbPeersSnubbed(cntPeersSnubbed);
}
/**
* Private method to process the results given by DiskManager's
* piece checking thread via asyncPieceChecked(..)
*/
private void
processPieceChecks()
{
if ( piece_check_result_list.size() > 0 ){
final List pieces;
// process complete piece results
try{
piece_check_result_list_mon.enter();
pieces = new ArrayList( piece_check_result_list );
piece_check_result_list.clear();
}finally{
piece_check_result_list_mon.exit();
}
final Iterator it = pieces.iterator();
while (it.hasNext()) {
final Object[] data = (Object[])it.next();
processPieceCheckResult((DiskManagerCheckRequest)data[0],((Integer)data[1]).intValue());
}
}
}
private void
checkBadPieces()
{
if ( mainloop_loop_count % MAINLOOP_SIXTY_SECOND_INTERVAL == 0 ){
if ( bad_piece_reported != -1 ){
DiskManagerCheckRequest req =
disk_mgr.createCheckRequest(
bad_piece_reported,
new Integer( CHECK_REASON_BAD_PIECE_CHECK ));
req.setLowPriority( true );
if ( Logger.isEnabled()){
Logger.log(
new LogEvent(
disk_mgr.getTorrent(), LOGID,
"Rescanning reported-bad piece " + bad_piece_reported ));
}
bad_piece_reported = -1;
try{
disk_mgr.enqueueCheckRequest( req, this );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
}
private void
checkRescan()
{
if ( rescan_piece_time == 0 ){
// pending a piece completion
return;
}
if ( next_rescan_piece == -1 ){
if ( mainloop_loop_count % MAINLOOP_FIVE_SECOND_INTERVAL == 0 ){
if ( adapter.isPeriodicRescanEnabled()){
next_rescan_piece = 0;
}
}
}else{
if ( mainloop_loop_count % MAINLOOP_TEN_MINUTE_INTERVAL == 0 ){
if ( !adapter.isPeriodicRescanEnabled()){
next_rescan_piece = -1;
}
}
}
if ( next_rescan_piece == -1 ){
return;
}
// delay as required
final long now = SystemTime.getCurrentTime();
if ( rescan_piece_time > now ){
rescan_piece_time = now;
}
// 250K/sec limit
final long piece_size = disk_mgr.getPieceLength();
final long millis_per_piece = piece_size / 250;
if ( now - rescan_piece_time < millis_per_piece ){
return;
}
while( next_rescan_piece != -1 ){
int this_piece = next_rescan_piece;
next_rescan_piece++;
if ( next_rescan_piece == _nbPieces ){
next_rescan_piece = -1;
}
// this functionality is to pick up pieces that have been downloaded OUTSIDE of
// Azureus - e.g. when two torrents are sharing a single file. Hence the check on
// the piece NOT being done
if ( pePieces[this_piece] == null && !dm_pieces[this_piece].isDone() && dm_pieces[this_piece].isNeeded()){
DiskManagerCheckRequest req =
disk_mgr.createCheckRequest(
this_piece,
new Integer( CHECK_REASON_SCAN ));
req.setLowPriority( true );
if ( Logger.isEnabled()){
Logger.log(
new LogEvent(
disk_mgr.getTorrent(), LOGID,
"Rescanning piece " + this_piece ));
}
rescan_piece_time = 0; // mark as check piece in process
try{
disk_mgr.enqueueCheckRequest( req, this );
}catch( Throwable e ){
rescan_piece_time = now;
Debug.printStackTrace(e);
}
break;
}
}
}
public void
badPieceReported(
PEPeerTransport originator,
int piece_number )
{
Debug.outNoStack( getDisplayName() + ": bad piece #" + piece_number + " reported by " + originator.getIp());
if ( piece_number < 0 || piece_number >= _nbPieces ){
return;
}
bad_piece_reported = piece_number;
}
public void
setStatsReceiver(
PEPeerManager.StatsReceiver receiver )
{
stats_receiver = receiver;
}
public void
statsRequest(
PEPeerTransport originator,
Map request )
{
Map reply = new HashMap();
adapter.statsRequest( originator, request, reply );
if ( reply.size() > 0 ){
originator.sendStatsReply( reply );
}
}
public void
statsReply(
PEPeerTransport originator,
Map reply )
{
PEPeerManager.StatsReceiver receiver = stats_receiver;
if ( receiver != null ){
receiver.receiveStats( originator, reply );
}
}
/**
* This method checks if the downloading process is finished.
*
*/
private void
checkFinished(
boolean start_of_day )
{
final boolean all_pieces_done =disk_mgr.getRemainingExcludingDND() ==0;
if (all_pieces_done)
{
seeding_mode = true;
prefer_udp_bloom = null;
piecePicker.clearEndGameChunks();
if (!start_of_day)
adapter.setStateFinishing();
_timeFinished = SystemTime.getCurrentTime();
final ArrayList peer_transports = peer_transports_cow;
//remove previous snubbing
for (int i =0; i <peer_transports.size(); i++ )
{
final PEPeerTransport pc = (PEPeerTransport) peer_transports.get(i);
pc.setSnubbed(false);
}
setNbPeersSnubbed(0);
final boolean checkPieces =COConfigurationManager.getBooleanParameter( "Check Pieces on Completion" );
//re-check all pieces to make sure they are not corrupt, but only if we weren't already complete
if (checkPieces &&!start_of_day)
{
final DiskManagerCheckRequest req =disk_mgr.createCheckRequest(-1, new Integer(CHECK_REASON_COMPLETE));
disk_mgr.enqueueCompleteRecheckRequest(req, this);
}
_timeStartedSeeding = SystemTime.getCurrentTime();
try{
disk_mgr.saveResumeData(false);
}catch( Throwable e ){
Debug.out( "Failed to save resume data", e );
}
adapter.setStateSeeding( start_of_day );
disk_mgr.downloadEnded();
} else
{
seeding_mode = false;
}
}
protected void
checkCompletionState()
{
if ( mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL != 0 ){
return;
}
boolean dm_done = disk_mgr.getRemainingExcludingDND() == 0;
if ( seeding_mode ){
if ( !dm_done ){
seeding_mode = false;
_timeStartedSeeding = -1;
_timeFinished = 0;
Logger.log(
new LogEvent( disk_mgr.getTorrent(), LOGID,
"Turning off seeding mode for PEPeerManager"));
}
}else{
if ( dm_done ){
checkFinished( false );
if ( seeding_mode ){
Logger.log(
new LogEvent( disk_mgr.getTorrent(), LOGID,
"Turning on seeding mode for PEPeerManager"));
}
}
}
}
/**
* This method will locate expired requests on peers, will cancel them,
* and mark the peer as snubbed if we haven't received usefull data from
* them within the last 60 seconds
*/
private void checkRequests()
{
// to be honest I don't see why this can't be 5 seconds, but I'm trying 1 second
// now as the existing 0.1 second is crazy given we're checking for events that occur
// at 60+ second intervals
if ( mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL != 0 ){
return;
}
final long now =SystemTime.getCurrentTime();
//for every connection
final ArrayList peer_transports = peer_transports_cow;
for (int i =peer_transports.size() -1; i >=0 ; i--)
{
final PEPeerTransport pc =(PEPeerTransport)peer_transports.get(i);
if (pc.getPeerState() ==PEPeer.TRANSFERING)
{
final List expired = pc.getExpiredRequests();
if (expired !=null &&expired.size() >0)
{ // now we know there's a request that's > 60 seconds old
final boolean isSeed =pc.isSeed();
// snub peers that haven't sent any good data for a minute
final long timeSinceGoodData =pc.getTimeSinceGoodDataReceived();
if (timeSinceGoodData <0 ||timeSinceGoodData >60 *1000)
pc.setSnubbed(true);
//Only cancel first request if more than 2 mins have passed
DiskManagerReadRequest request =(DiskManagerReadRequest) expired.get(0);
final long timeSinceData =pc.getTimeSinceLastDataMessageReceived();
final boolean noData =(timeSinceData <0) ||timeSinceData >(1000 *(isSeed ?120 :60));
final long timeSinceOldestRequest = now - request.getTimeCreated(now);
//for every expired request
for (int j = (timeSinceOldestRequest >120 *1000 && noData) ? 0 : 1; j < expired.size(); j++)
{
//get the request object
request =(DiskManagerReadRequest) expired.get(j);
//Only cancel first request if more than 2 mins have passed
pc.sendCancel(request); //cancel the request object
//get the piece number
final int pieceNumber = request.getPieceNumber();
PEPiece pe_piece = pePieces[pieceNumber];
//unmark the request on the block
if ( pe_piece != null )
pe_piece.clearRequested(request.getOffset() /DiskManager.BLOCK_SIZE);
// remove piece if empty so peers can choose something else, except in end game
if (!piecePicker.isInEndGameMode())
checkEmptyPiece(pieceNumber);
}
}
}
}
}
private void
updateTrackerAnnounceInterval()
{
if ( mainloop_loop_count % MAINLOOP_FIVE_SECOND_INTERVAL != 0 ){
return;
}
final int WANT_LIMIT = 100;
int num_wanted = getMaxNewConnectionsAllowed();
final boolean has_remote = adapter.isNATHealthy();
if( has_remote ) {
//is not firewalled, so can accept incoming connections,
//which means no need to continually keep asking the tracker for peers
num_wanted = (int)(num_wanted / 1.5);
}
if ( num_wanted < 0 || num_wanted > WANT_LIMIT ) {
num_wanted = WANT_LIMIT;
}
int current_connection_count = PeerIdentityManager.getIdentityCount( _hash );
final TRTrackerScraperResponse tsr = adapter.getTrackerScrapeResponse();
if( tsr != null && tsr.isValid() ) { //we've got valid scrape info
final int num_seeds = tsr.getSeeds();
final int num_peers = tsr.getPeers();
final int swarm_size;
if( seeding_mode ) {
//Only use peer count when seeding, as other seeds are unconnectable.
//Since trackers return peers randomly (some of which will be seeds),
//backoff by the seed2peer ratio since we're given only that many peers
//on average each announce.
final float ratio = (float)num_peers / (num_seeds + num_peers);
swarm_size = (int)(num_peers * ratio);
}
else {
swarm_size = num_peers + num_seeds;
}
if( swarm_size < num_wanted ) { //lower limit to swarm size if necessary
num_wanted = swarm_size;
}
}
if( num_wanted < 1 ) { //we dont need any more connections
adapter.setTrackerRefreshDelayOverrides( 100 ); //use normal announce interval
return;
}
if( current_connection_count == 0 ) current_connection_count = 1; //fudge it :)
final int current_percent = (current_connection_count * 100) / (current_connection_count + num_wanted);
adapter.setTrackerRefreshDelayOverrides( current_percent ); //set dynamic interval override
}
public boolean
hasDownloadablePiece()
{
return( piecePicker.hasDownloadablePiece());
}
public int
getBytesQueuedForUpload()
{
return( bytes_queued_for_upload );
}
public int
getNbPeersWithUploadQueued()
{
return( connections_with_queued_data );
}
public int
getNbPeersWithUploadBlocked()
{
return( connections_with_queued_data_blocked );
}
public int
getNbPeersUnchoked()
{
return( connections_unchoked );
}
public int[] getAvailability()
{
return piecePicker.getAvailability();
}
//this only gets called when the My Torrents view is displayed
public float getMinAvailability()
{
return piecePicker.getMinAvailability();
}
public float getAvgAvail()
{
return piecePicker.getAvgAvail();
}
public long getAvailWentBadTime()
{
return( piecePicker.getAvailWentBadTime());
}
public void addPeerTransport( PEPeerTransport transport ) {
if (!ip_filter.isInRange(transport.getIp(), getDisplayName(), getTorrentHash())) {
final ArrayList peer_transports = peer_transports_cow;
if (!peer_transports.contains( transport )) {
addToPeerTransports(transport);
}
else{
Debug.out( "addPeerTransport():: peer_transports.contains(transport): SHOULD NEVER HAPPEN !" );
transport.closeConnection( "already connected" );
}
}
else {
transport.closeConnection( "IP address blocked by filters" );
}
}
/**
* Do all peer choke/unchoke processing.
*/
private void doUnchokes() {
// logic below is either 1 second or 10 secondly, bail out early id neither
if( !UploadSlotManager.AUTO_SLOT_ENABLE ) { //manual per-torrent unchoke slot mode
if( mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL != 0 ) {
return;
}
final int max_to_unchoke = adapter.getMaxUploads(); //how many simultaneous uploads we should consider
final ArrayList peer_transports = peer_transports_cow;
//determine proper unchoker
if( seeding_mode ) {
if( unchoker == null || !(unchoker.isSeedingUnchoker()) ) {
unchoker = UnchokerFactory.getSingleton().getUnchoker( true );
}
}
else {
if( unchoker == null || unchoker.isSeedingUnchoker()) {
unchoker = UnchokerFactory.getSingleton().getUnchoker( false );
}
}
//do main choke/unchoke update every 10 secs
if ( mainloop_loop_count % MAINLOOP_TEN_SECOND_INTERVAL == 0 ){
final boolean refresh = mainloop_loop_count % MAINLOOP_THIRTY_SECOND_INTERVAL == 0;
unchoker.calculateUnchokes( max_to_unchoke, peer_transports, refresh, adapter.hasPriorityConnection());
ArrayList chokes = unchoker.getChokes();
ArrayList unchokes = unchoker.getUnchokes();
addFastUnchokes( unchokes );
UnchokerUtil.performChokes( chokes, unchokes );
}else if ( mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL == 0 ) { //do quick unchoke check every 1 sec
ArrayList unchokes = unchoker.getImmediateUnchokes( max_to_unchoke, peer_transports );
addFastUnchokes( unchokes );
UnchokerUtil.performChokes( null, unchokes );
}
}
}
private void
addFastUnchokes(
ArrayList peers_to_unchoke )
{
for( Iterator it=peer_transports_cow.iterator();it.hasNext();) {
PEPeerTransport peer = (PEPeerTransport)it.next();
if ( peer.getConnectionState() != PEPeerTransport.CONNECTION_FULLY_ESTABLISHED ||
!UnchokerUtil.isUnchokable( peer, true ) ||
peers_to_unchoke.contains( peer )){
continue;
}
if( peer.isLANLocal()){
peers_to_unchoke.add( peer );
}else if ( fast_unchoke_new_peers &&
peer.getData( "fast_unchoke_done" ) == null ){
peer.setData( "fast_unchoke_done", "" );
peers_to_unchoke.add( peer );
}
}
}
// send the have requests out
private void sendHave(int pieceNumber) {
//fo
final ArrayList peer_transports = peer_transports_cow;
for (int i = 0; i < peer_transports.size(); i++) {
//get a peer connection
final PEPeerTransport pc = (PEPeerTransport) peer_transports.get(i);
//send the have message
pc.sendHave(pieceNumber);
}
}
// Method that checks if we are connected to another seed, and if so, disconnect from him.
private void checkSeeds() {
//proceed on mainloop 1 second intervals if we're a seed and we want to force disconnects
if ((mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL) != 0)
return;
if (!disconnect_seeds_when_seeding ){
return;
}
ArrayList to_close = null;
final ArrayList peer_transports = peer_transports_cow;
for (int i = 0; i < peer_transports.size(); i++) {
final PEPeerTransport pc = (PEPeerTransport) peer_transports.get(i);
if (pc != null && pc.getPeerState() == PEPeer.TRANSFERING && ((isSeeding() && pc.isSeed()) || pc.isRelativeSeed())) {
if( to_close == null ) to_close = new ArrayList();
to_close.add( pc );
}
}
if( to_close != null ) {
for( int i=0; i < to_close.size(); i++ ) {
closeAndRemovePeer( (PEPeerTransport)to_close.get(i), "disconnect other seed when seeding", false );
}
}
}
private void updateStats() {
if ( (mainloop_loop_count %MAINLOOP_ONE_SECOND_INTERVAL) != 0 ){
return;
}
//calculate seeds vs peers
final ArrayList<PEPeer> peer_transports = peer_transports_cow;
int new_pending_tcp_connections = 0;
int new_connecting_tcp_connections = 0;
int new_seeds = 0;
int new_peers = 0;
int new_tcp_incoming = 0;
int new_udp_incoming = 0;
int bytes_queued = 0;
int con_queued = 0;
int con_blocked = 0;
int con_unchoked = 0;
for ( Iterator<PEPeer> it=peer_transports.iterator();it.hasNext();){
final PEPeerTransport pc = (PEPeerTransport) it.next();
if ( pc.getPeerState() == PEPeer.TRANSFERING) {
if ( !pc.isChokedByMe()){
con_unchoked++;
}
Connection connection = pc.getPluginConnection();
if ( connection != null ){
OutgoingMessageQueue mq = connection.getOutgoingMessageQueue();
int q = mq.getDataQueuedBytes() + mq.getProtocolQueuedBytes();
bytes_queued += q;
if ( q > 0 ){
con_queued++;
if ( mq.isBlocked()){
con_blocked++;
}
}
}
if (pc.isSeed())
new_seeds++;
else
new_peers++;
if ( pc.isIncoming() && !pc.isLANLocal()){
if ( pc.isTCP() ) {
new_tcp_incoming++;
}else{
new_udp_incoming++;
}
}
}else{
if ( pc.isTCP()){
int c_state = pc.getConnectionState();
if ( c_state == PEPeerTransport.CONNECTION_PENDING ){
new_pending_tcp_connections++;
}else if ( c_state == PEPeerTransport.CONNECTION_CONNECTING ){
new_connecting_tcp_connections++;
}
}
}
}
_seeds = new_seeds;
_peers = new_peers;
_remotesTCPNoLan = new_tcp_incoming;
_remotesUDPNoLan = new_udp_incoming;
_tcpPendingConnections = new_pending_tcp_connections;
_tcpConnectingConnections = new_connecting_tcp_connections;
bytes_queued_for_upload = bytes_queued;
connections_with_queued_data = con_queued;
connections_with_queued_data_blocked = con_blocked;
connections_unchoked = con_unchoked;
}
/**
* The way to unmark a request as being downloaded, or also
* called by Peer connections objects when connection is closed or choked
* @param request a DiskManagerReadRequest holding details of what was canceled
*/
public void requestCanceled(DiskManagerReadRequest request)
{
final int pieceNumber =request.getPieceNumber(); //get the piece number
PEPiece pe_piece = pePieces[pieceNumber];
if (pe_piece != null ){
pe_piece.clearRequested(request.getOffset() /DiskManager.BLOCK_SIZE);
}
}
public PEPeerControl
getControl()
{
return( this );
}
public byte[][]
getSecrets(
int crypto_level )
{
return( adapter.getSecrets( crypto_level ));
}
// get the hash value
public byte[] getHash() {
return _hash.getDataID();
}
public PeerIdentityDataID
getPeerIdentityDataID()
{
return( _hash );
}
// get the peer id value
public byte[] getPeerId() {
return _myPeerId;
}
// get the remaining percentage
public long getRemaining() {
return disk_mgr.getRemaining();
}
public void discarded(PEPeer peer, int length) {
if (length > 0){
_stats.discarded(peer, length);
// discards are more likely during end-game-mode
if ( ban_peer_discard_ratio > 0 && !( piecePicker.isInEndGameMode() || piecePicker.hasEndGameModeBeenAbandoned())){
long received = peer.getStats().getTotalDataBytesReceived();
long discarded = peer.getStats().getTotalBytesDiscarded();
long non_discarded = received - discarded;
if ( non_discarded < 0 ){
non_discarded = 0;
}
if ( discarded >= ban_peer_discard_min_kb * 1024 ){
if ( non_discarded == 0 ||
((float)discarded) / non_discarded >= ban_peer_discard_ratio ){
badPeerDetected( peer.getIp(), -1 );
}
}
}
}
}
public void dataBytesReceived(PEPeer peer, int length) {
if (length > 0) {
_stats.dataBytesReceived(peer,length);
_averageReceptionSpeed.addValue(length);
}
}
public void protocolBytesReceived(PEPeer peer, int length ) {
if (length > 0) {
_stats.protocolBytesReceived(peer,length);
}
}
public void dataBytesSent(PEPeer peer, int length) {
if (length > 0) {
_stats.dataBytesSent(peer, length );
}
}
public void protocolBytesSent( PEPeer peer, int length ) {
if (length > 0) {
_stats.protocolBytesSent(peer,length);
}
}
/** DiskManagerWriteRequestListener message
* @see org.gudy.azureus2.core3.disk.DiskManagerWriteRequestListener
*/
public void writeCompleted(DiskManagerWriteRequest request)
{
final int pieceNumber =request.getPieceNumber();
DiskManagerPiece dm_piece = dm_pieces[pieceNumber];
if (!dm_piece.isDone()){
final PEPiece pePiece =pePieces[pieceNumber];
if (pePiece !=null){
pePiece.setWritten((PEPeer)request.getUserData(), request.getOffset() /DiskManager.BLOCK_SIZE );
}else{
// this is a way of fixing a 99.9% bug where a dmpiece is left in a
// fully downloaded state with the underlying pe_piece null. Possible explanation is
// that a slow peer sends an entire piece at around the time a pe_piece gets reset
// due to inactivity.
// we also get here when recovering data that has come in late after the piece has
// been abandoned
dm_piece.setWritten( request.getOffset() /DiskManager.BLOCK_SIZE );
}
}
}
public void
writeFailed(
DiskManagerWriteRequest request,
Throwable cause )
{
// if the write has failed then the download will have been stopped so there is no need to try
// and reset the piece
}
/**
*
* @param data_offset MUST be block aligned within the TORRENT
* @param data
* @return buffers not yet written
* @throws Exception
*/
public DirectByteBuffer[]
write(
long data_offset,
DirectByteBuffer[] data )
throws Exception
{
if ( data_offset % DiskManager.BLOCK_SIZE != 0 ){
throw( new Exception( "data must start at a block offset" ));
}
int piece_length = disk_mgr.getPieceLength();
int data_length = 0;
for( DirectByteBuffer buffer: data ){
data_length += buffer.remaining( DirectByteBuffer.SS_DW );
}
long written = 0;
int buffer_number = 0;
for ( int i=0;i<data_length;i+=DiskManager.BLOCK_SIZE ){
int rem = data_length - i;
if ( rem > DiskManager.BLOCK_SIZE ){
rem = DiskManager.BLOCK_SIZE;
}
int piece_number = (int)( data_offset / piece_length );
DiskManagerPiece dm_piece = dm_pieces[piece_number];
int block_number = (int)(data_offset - ( piece_number * piece_length )) / DiskManager.BLOCK_SIZE;
int block_size = dm_piece.getBlockSize( block_number );
if ( rem < block_size ){
break;
}
DirectByteBuffer chunk = DirectByteBufferPool.getBuffer( DirectByteBuffer.AL_BT_PIECE, block_size );
writeBlock( piece_number, block_number*DiskManager.BLOCK_SIZE, chunk, null, true );
written += rem;
data_offset += rem;
}
List<DirectByteBuffer> unwritten = new ArrayList<DirectByteBuffer>();
for (int i=buffer_number;i<data.length;i++){
if (data[i].hasRemaining( DirectByteBuffer.SS_DW )){
unwritten.add( data[i] );
}else{
data[i].returnToPool();
}
}
return( unwritten.toArray( new DirectByteBuffer[ unwritten.size()] ));
}
/** This method will queue up a dism manager write request for the block if the block is not already written.
* It will send out cancels for the block to all peer either if in end-game mode, or per cancel param
* @param pieceNumber to potentialy write to
* @param offset within piece to queue write for
* @param data to be writen
* @param sender peer that sent this data
* @param cancel if cancels definatly need to be sent to all peers for this request
*/
public void writeBlock(int pieceNumber, int offset, DirectByteBuffer data, PEPeer sender, boolean cancel)
{
final int blockNumber =offset /DiskManager.BLOCK_SIZE;
final DiskManagerPiece dmPiece =dm_pieces[pieceNumber];
if (dmPiece.isWritten(blockNumber))
{
data.returnToPool();
return;
}
PEPiece pe_piece = pePieces[ pieceNumber ];
if ( pe_piece != null ){
pe_piece.setDownloaded( offset );
}
final DiskManagerWriteRequest request =disk_mgr.createWriteRequest(pieceNumber, offset, data, sender);
disk_mgr.enqueueWriteRequest(request, this );
// In case we are in endGame mode, remove the block from the chunk list
if (piecePicker.isInEndGameMode())
piecePicker.removeFromEndGameModeChunks(pieceNumber, offset);
if (cancel ||piecePicker.isInEndGameMode())
{ // cancel any matching outstanding download requests
//For all connections cancel the request
final ArrayList peer_transports = peer_transports_cow;
for (int i=0;i<peer_transports.size();i++)
{
final PEPeerTransport connection =(PEPeerTransport) peer_transports.get(i);
final DiskManagerReadRequest dmr =disk_mgr.createReadRequest(pieceNumber, offset, dmPiece.getBlockSize(blockNumber));
connection.sendCancel( dmr );
}
}
}
// /**
// * This method is only called when a block is received after the initial request expired,
// * but the data has not yet been fulfilled by any other peer, so we use the block data anyway
// * instead of throwing it away, and cancel any outstanding requests for that block that might have
// * been sent after initial expiry.
// */
// public void writeBlockAndCancelOutstanding(int pieceNumber, int offset, DirectByteBuffer data,PEPeer sender) {
// final int blockNumber =offset /DiskManager.BLOCK_SIZE;
// final DiskManagerPiece dmPiece =dm_pieces[pieceNumber];
// if (dmPiece.isWritten(blockNumber))
// {
// data.returnToPool();
// return;
// }
// DiskManagerWriteRequest request =disk_mgr.createWriteRequest(pieceNumber, offset, data, sender);
// disk_mgr.enqueueWriteRequest(request, this);
// // cancel any matching outstanding download requests
// List peer_transports =peer_transports_cow;
// for (int i =0; i <peer_transports.size(); i++)
// {
// PEPeerTransport connection =(PEPeerTransport) peer_transports.get(i);
// DiskManagerReadRequest dmr =disk_mgr.createReadRequest(pieceNumber, offset, dmPiece.getBlockSize(blockNumber));
// connection.sendCancel(dmr);
// }
// }
public boolean isWritten(int piece_number, int offset)
{
return dm_pieces[piece_number].isWritten(offset /DiskManager.BLOCK_SIZE);
}
public boolean
validateReadRequest(
PEPeerTransport originator,
int pieceNumber,
int offset,
int length)
{
if ( disk_mgr.checkBlockConsistencyForRead(originator.getClient() + ": " + originator.getIp(), true, pieceNumber, offset, length)){
if ( enable_seeding_piece_rechecks && isSeeding()){
DiskManagerPiece dm_piece = dm_pieces[pieceNumber];
int read_count = dm_piece.getReadCount()&0xffff;
if ( read_count < SEED_CHECK_WAIT_MARKER - 1 ){
read_count++;
dm_piece.setReadCount((short)read_count );
}
}
return( true );
}else{
return( false );
}
}
public boolean
validateHintRequest(
PEPeerTransport originator,
int pieceNumber,
int offset,
int length)
{
return( disk_mgr.checkBlockConsistencyForHint(originator.getClient() + ": " + originator.getIp(),pieceNumber, offset, length ));
}
public boolean
validatePieceReply(
PEPeerTransport originator,
int pieceNumber,
int offset,
DirectByteBuffer data)
{
return disk_mgr.checkBlockConsistencyForWrite(originator.getClient() + ": " + originator.getIp(),pieceNumber, offset, data);
}
public int getAvailability(int pieceNumber)
{
return piecePicker.getAvailability(pieceNumber);
}
public void havePiece(int pieceNumber, int pieceLength, PEPeer pcOrigin) {
piecePicker.addHavePiece(pcOrigin, pieceNumber);
_stats.haveNewPiece(pieceLength);
if(superSeedMode) {
superSeedPieces[pieceNumber].peerHasPiece(pcOrigin);
if(pieceNumber == pcOrigin.getUniqueAnnounce()) {
pcOrigin.setUniqueAnnounce(-1);
superSeedModeNumberOfAnnounces--;
}
}
int availability =piecePicker.getAvailability(pieceNumber) -1;
if (availability < 4) {
if (dm_pieces[pieceNumber].isDone())
availability--;
if (availability <= 0)
return;
//for all peers
final ArrayList peer_transports = peer_transports_cow;
for (int i = peer_transports.size() - 1; i >= 0; i--) {
final PEPeerTransport pc = (PEPeerTransport) peer_transports.get(i);
if (pc !=pcOrigin &&pc.getPeerState() ==PEPeer.TRANSFERING &&pc.isPieceAvailable(pieceNumber))
((PEPeerStatsImpl)pc.getStats()).statisticalSentPiece(pieceLength / availability);
}
}
}
public int getPieceLength(int pieceNumber) {
return disk_mgr.getPieceLength(pieceNumber);
}
public int
getNbPeers()
{
return _peers;
}
public int getNbSeeds()
{
return _seeds;
}
public int getNbRemoteTCPConnections()
{
return _remotesTCPNoLan;
}
public int getNbRemoteUDPConnections()
{
return _remotesUDPNoLan;
}
public long getLastRemoteConnectionTime()
{
return( last_remote_time );
}
public PEPeerManagerStats getStats() {
return _stats;
}
public int
getNbPeersStalledPendingLoad()
{
int res = 0;
Iterator it = peer_transports_cow.iterator();
while( it.hasNext()){
PEPeerTransport transport = (PEPeerTransport)it.next();
if ( transport.isStalledPendingLoad()){
res ++;
}
}
return( res );
}
/**
* Returns the ETA time in seconds.
* If the returned time is 0, the download is complete.
* If the returned time is negative, the download
* is complete and it took -xxx time to complete.
*/
public long
getETA()
{
final long now = SystemTime.getCurrentTime();
if ( now < last_eta_calculation || now - last_eta_calculation > 900 ){
long dataRemaining = disk_mgr.getRemainingExcludingDND();
if ( dataRemaining > 0 ){
int writtenNotChecked = 0;
for (int i = 0; i < _nbPieces; i++)
{
if (dm_pieces[i].isInteresting()){
writtenNotChecked +=dm_pieces[i].getNbWritten() *DiskManager.BLOCK_SIZE;
}
}
dataRemaining = dataRemaining - writtenNotChecked;
if (dataRemaining < 0 ){
dataRemaining = 0;
}
}
long result;
if (dataRemaining == 0) {
final long timeElapsed = (_timeFinished - _timeStarted)/1000;
//if time was spent downloading....return the time as negative
if(timeElapsed > 1){
result = timeElapsed * -1;
}else{
result = 0;
}
}else{
final long averageSpeed = _averageReceptionSpeed.getAverage();
long lETA = (averageSpeed == 0) ? Constants.CRAPPY_INFINITE_AS_LONG : dataRemaining / averageSpeed;
// stop the flickering of ETA from "Finished" to "x seconds" when we are
// just about complete, but the data rate is jumpy.
if (lETA == 0)
lETA = 1;
result = lETA;
}
last_eta = result;
last_eta_calculation = now;
}
return( last_eta );
}
public boolean
isRTA()
{
return( piecePicker.getRTAProviders().size() > 0 );
}
private void
addToPeerTransports(
PEPeerTransport peer )
{
boolean added = false;
List limiters;
try{
peer_transports_mon.enter();
// if it is already disconnected (synchronous failure during connect
// for example) don't add it
if ( peer.getPeerState() == PEPeer.DISCONNECTED ){
return;
}
if( peer_transports_cow.contains( peer ) ){
Debug.out( "Transport added twice" );
return; //we do not want to close it
}
if( is_running ) {
//copy-on-write semantics
final ArrayList new_peer_transports = new ArrayList(peer_transports_cow.size() +1);
new_peer_transports.addAll( peer_transports_cow );
new_peer_transports.add( peer );
peer_transports_cow = new_peer_transports;
added = true;
}
limiters = external_rate_limiters_cow;
}
finally{
peer_transports_mon.exit();
}
if( added ) {
boolean incoming = peer.isIncoming();
_stats.haveNewConnection( incoming );
if ( incoming ){
long connect_time = SystemTime.getCurrentTime();
if ( connect_time > last_remote_time ){
last_remote_time = connect_time;
}
}
if ( limiters != null ){
for (int i=0;i<limiters.size();i++){
Object[] entry = (Object[])limiters.get(i);
peer.addRateLimiter((LimitedRateGroup)entry[0],((Boolean)entry[1]).booleanValue());
}
}
peerAdded( peer );
}
else {
peer.closeConnection( "PeerTransport added when manager not running" );
}
}
public void
addRateLimiter(
LimitedRateGroup group,
boolean upload )
{
List<PEPeer> transports;
try{
peer_transports_mon.enter();
ArrayList<Object[]> new_limiters = new ArrayList<Object[]>( external_rate_limiters_cow==null?1:external_rate_limiters_cow.size()+1);
if ( external_rate_limiters_cow != null ){
new_limiters.addAll( external_rate_limiters_cow );
}
new_limiters.add( new Object[]{ group, new Boolean( upload )});
external_rate_limiters_cow = new_limiters;
transports = peer_transports_cow;
}finally{
peer_transports_mon.exit();
}
for (int i=0;i<transports.size();i++){
transports.get(i).addRateLimiter( group, upload );
}
}
public void
removeRateLimiter(
LimitedRateGroup group,
boolean upload )
{
List transports;
try{
peer_transports_mon.enter();
if ( external_rate_limiters_cow != null ){
ArrayList new_limiters = new ArrayList( external_rate_limiters_cow.size()-1);
for (int i=0;i<external_rate_limiters_cow.size();i++){
Object[] entry = (Object[])external_rate_limiters_cow.get(i);
if ( entry[0] != group ){
new_limiters.add( entry );
}
}
if ( new_limiters.size() == 0 ){
external_rate_limiters_cow = null;
}else{
external_rate_limiters_cow = new_limiters;
}
}
transports = peer_transports_cow;
}finally{
peer_transports_mon.exit();
}
for (int i=0;i<transports.size();i++){
((PEPeerTransport)transports.get(i)).removeRateLimiter( group, upload );
}
}
public int
getUploadRateLimitBytesPerSecond()
{
return( adapter.getUploadRateLimitBytesPerSecond());
}
public int
getDownloadRateLimitBytesPerSecond()
{
return( adapter.getDownloadRateLimitBytesPerSecond());
}
// the peer calls this method itself in closeConnection() to notify this manager
public void
peerConnectionClosed(
PEPeerTransport peer,
boolean connect_failed,
boolean network_failed )
{
boolean connection_found = false;
boolean tcpReconnect = false;
boolean ipv6reconnect = false;
try{
peer_transports_mon.enter();
int udpPort = peer.getUDPListenPort();
boolean canTryUDP = UDPNetworkManager.UDP_OUTGOING_ENABLED && peer.getUDPListenPort() > 0;
boolean canTryIpv6 = NetworkAdmin.getSingleton().hasIPV6Potential(true) && peer.getAlternativeIPv6() != null;
if ( is_running ){
PeerItem peer_item = peer.getPeerItemIdentity();
PeerItem self_item = peer_database.getSelfPeer();
if ( self_item == null || !self_item.equals( peer_item )){
String ip = peer.getIp();
boolean wasIPv6;
try
{
wasIPv6 = InetAddress.getByName(ip) instanceof Inet6Address;
} catch (UnknownHostException e)
{
wasIPv6 = false;
// something is fishy about the old address, don't try to reconnect with v6
canTryIpv6 = false;
}
//System.out.println("netfail="+network_failed+", connfail="+connect_failed+", can6="+canTryIpv6+", was6="+wasIPv6);
String key = ip + ":" + udpPort;
if ( peer.isTCP()){
if ( connect_failed ){
// TCP connect failure, try UDP later if necessary
if ( canTryUDP && udp_fallback_for_failed_connection ){
pending_nat_traversals.put(key, peer);
} else if (canTryIpv6 && !wasIPv6)
{
tcpReconnect = true;
ipv6reconnect = true;
}
}else if ( canTryUDP &&
udp_fallback_for_dropped_connection &&
network_failed &&
seeding_mode &&
peer.isInterested() &&
!peer.isSeed() && !peer.isRelativeSeed() &&
peer.getStats().getEstimatedSecondsToCompletion() > 60 &&
FeatureAvailability.isUDPPeerReconnectEnabled()){
if (Logger.isEnabled()){
Logger.log(new LogEvent(peer, LOGID, LogEvent.LT_WARNING, "Unexpected stream closure detected, attempting recovery"));
}
// System.out.println( "Premature close of stream: " + getDisplayName() + "/" + peer.getIp());
udp_reconnects.put( key, peer );
}else if ( network_failed &&
peer.isSafeForReconnect() &&
!(seeding_mode && (peer.isSeed() || peer.isRelativeSeed() || peer.getStats().getEstimatedSecondsToCompletion() < 60)) &&
getMaxConnections() > 0 &&
getMaxNewConnectionsAllowed() > getMaxConnections() / 3 &&
FeatureAvailability.isGeneralPeerReconnectEnabled()){
tcpReconnect = true;
}
}else if ( connect_failed ){
// UDP connect failure
if ( udp_fallback_for_failed_connection ){
if ( peer.getData(PEER_NAT_TRAVERSE_DONE_KEY) == null){
// System.out.println( "Direct reconnect failed, attempting NAT traversal" );
pending_nat_traversals.put(key, peer);
}
}
}
}
}
if( peer_transports_cow.contains( peer )) {
final ArrayList new_peer_transports = new ArrayList( peer_transports_cow );
new_peer_transports.remove(peer);
peer_transports_cow = new_peer_transports;
connection_found = true;
}
}
finally{
peer_transports_mon.exit();
}
if ( connection_found ){
if( peer.getPeerState() != PEPeer.DISCONNECTED ) {
System.out.println( "peer.getPeerState() != PEPeer.DISCONNECTED: " +peer.getPeerState() );
}
peerRemoved( peer ); //notify listeners
}
if(tcpReconnect)
peer.reconnect(false, ipv6reconnect);
}
public void
peerAdded(
PEPeer pc)
{
adapter.addPeer(pc); //async downloadmanager notification
//sync peermanager notification
final ArrayList peer_manager_listeners = peer_manager_listeners_cow;
for( int i=0; i < peer_manager_listeners.size(); i++ ) {
((PEPeerManagerListener)peer_manager_listeners.get(i)).peerAdded( this, pc );
}
}
public void
peerRemoved(
PEPeer pc)
{
if ( is_running &&
!seeding_mode &&
( prefer_udp || prefer_udp_default )){
int udp = pc.getUDPListenPort();
if ( udp != 0 && udp == pc.getTCPListenPort()){
BloomFilter filter = prefer_udp_bloom;
if ( filter == null ){
filter = prefer_udp_bloom = BloomFilterFactory.createAddOnly( PREFER_UDP_BLOOM_SIZE );
}
if ( filter.getEntryCount() < PREFER_UDP_BLOOM_SIZE / 10 ){
filter.add( pc.getIp().getBytes());
}
}
}
final int piece = pc.getUniqueAnnounce();
if(piece != -1 && superSeedMode ) {
superSeedModeNumberOfAnnounces--;
superSeedPieces[piece].peerLeft();
}
int[] reserved_pieces = pc.getReservedPieceNumbers();
if ( reserved_pieces != null ){
for ( int reserved_piece: reserved_pieces ){
PEPiece pe_piece = pePieces[reserved_piece];
if ( pe_piece != null ){
String reserved_by = pe_piece.getReservedBy();
if ( reserved_by != null && reserved_by.equals( pc.getIp())){
pe_piece.setReservedBy( null );
}
}
}
}
adapter.removePeer(pc); //async downloadmanager notification
//sync peermanager notification
final ArrayList peer_manager_listeners = peer_manager_listeners_cow;
for( int i=0; i < peer_manager_listeners.size(); i++ ) {
((PEPeerManagerListener)peer_manager_listeners.get(i)).peerRemoved( this, pc );
}
}
/** Don't pass a null to this method. All activations of pieces must go through here.
* @param piece PEPiece invoked; notifications of it's invocation need to be done
* @param pieceNumber of the PEPiece
*/
public void addPiece(final PEPiece piece, final int pieceNumber, PEPeer for_peer )
{
addPiece( piece, pieceNumber, false, for_peer );
}
protected void addPiece(final PEPiece piece, final int pieceNumber, final boolean force_add, PEPeer for_peer )
{
pePieces[pieceNumber] =(PEPieceImpl)piece;
nbPiecesActive++;
if ( is_running || force_add ){
// deal with possible piece addition by scheduler loop after closdown started
adapter.addPiece(piece);
}
final ArrayList peer_manager_listeners = peer_manager_listeners_cow;
for( int i=0; i < peer_manager_listeners.size(); i++ ) {
try{
((PEPeerManagerListener)peer_manager_listeners.get(i)).pieceAdded(this, piece, for_peer );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
/** Sends messages to listeners that the piece is no longer active. All closing
* out (deactivation) of pieces must go through here. The piece will be null upon return.
* @param pePiece PEPiece to remove
* @param pieceNumber int
*/
public void removePiece(PEPiece pePiece, int pieceNumber) {
if ( pePiece != null ){
adapter.removePiece(pePiece);
} else {
pePiece = pePieces[pieceNumber];
}
pePieces[pieceNumber] =null;
nbPiecesActive--;
if (pePiece == null) {
Debug.outNoStack("Trying to remove piece " + pieceNumber + " when piece is null");
return;
}
final ArrayList peer_manager_listeners = peer_manager_listeners_cow;
for( int i=0; i < peer_manager_listeners.size(); i++ ) {
try{
((PEPeerManagerListener)peer_manager_listeners.get(i)).pieceRemoved(this, pePiece );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
public int getNbActivePieces()
{
return nbPiecesActive;
}
public String getElapsedTime() {
return TimeFormatter.format((SystemTime.getCurrentTime() - _timeStarted) / 1000);
}
// Returns time started in ms
public long getTimeStarted() {
return _timeStarted;
}
public long getTimeStartedSeeding() {
return _timeStartedSeeding;
}
private byte[] computeMd5Hash(DirectByteBuffer buffer)
{
BrokenMd5Hasher md5 = new BrokenMd5Hasher();
md5.reset();
final int position = buffer.position(DirectByteBuffer.SS_DW);
md5.update(buffer.getBuffer(DirectByteBuffer.SS_DW));
buffer.position(DirectByteBuffer.SS_DW, position);
ByteBuffer md5Result = ByteBuffer.allocate(16);
md5Result.position(0);
md5.finalDigest( md5Result );
final byte[] result =new byte[16];
md5Result.position(0);
for (int i =0; i <result.length; i++ )
{
result[i] = md5Result.get();
}
return result;
}
private void MD5CheckPiece(PEPiece piece, boolean correct)
{
final String[] writers =piece.getWriters();
int offset = 0;
for (int i =0; i <writers.length; i++ )
{
final int length =piece.getBlockSize(i);
final String peer =writers[i];
if (peer !=null)
{
DirectByteBuffer buffer =disk_mgr.readBlock(piece.getPieceNumber(), offset, length);
if (buffer !=null)
{
final byte[] hash =computeMd5Hash(buffer);
buffer.returnToPool();
buffer = null;
piece.addWrite(i,peer,hash,correct);
}
}
offset += length;
}
}
public void checkCompleted(DiskManagerCheckRequest request, boolean passed)
{
if ( TEST_PERIODIC_SEEDING_SCAN_FAIL_HANDLING && ((Integer) request.getUserData()).intValue() == CHECK_REASON_SEEDING_CHECK ){
passed = false;
}
try
{
piece_check_result_list_mon.enter();
piece_check_result_list.add(new Object[]{request, new Integer( passed?1:0 )});
} finally
{
piece_check_result_list_mon.exit();
}
}
public void checkCancelled(DiskManagerCheckRequest request)
{
try
{
piece_check_result_list_mon.enter();
piece_check_result_list.add(new Object[]{request, new Integer( 2 )});
} finally
{
piece_check_result_list_mon.exit();
}
}
public void checkFailed(DiskManagerCheckRequest request, Throwable cause)
{
try
{
piece_check_result_list_mon.enter();
piece_check_result_list.add(new Object[]{request, new Integer( 0 )});
} finally
{
piece_check_result_list_mon.exit();
}
}
public boolean needsMD5CheckOnCompletion(int pieceNumber)
{
final PEPieceImpl piece = pePieces[pieceNumber];
if(piece == null)
return false;
return piece.getPieceWrites().size() > 0;
}
private void processPieceCheckResult(DiskManagerCheckRequest request, int outcome)
{
final int check_type =((Integer) request.getUserData()).intValue();
try{
final int pieceNumber =request.getPieceNumber();
// System.out.println( "processPieceCheckResult(" + _finished + "/" + recheck_on_completion + "):" + pieceNumber +
// "/" + piece + " - " + result );
// passed = 1, failed = 0, cancelled = 2
if (check_type ==CHECK_REASON_COMPLETE){
// this is a recheck, so don't send HAVE msgs
if ( outcome == 0 ){
// piece failed; restart the download afresh
Debug.out(getDisplayName() + ": Piece #" +pieceNumber +" failed final re-check. Re-downloading...");
if (!restart_initiated){
restart_initiated = true;
adapter.restartDownload(true);
}
}
return;
}else if ( check_type == CHECK_REASON_SEEDING_CHECK || check_type == CHECK_REASON_BAD_PIECE_CHECK ){
if ( outcome == 0 ){
if ( check_type == CHECK_REASON_SEEDING_CHECK ){
Debug.out(getDisplayName() + "Piece #" +pieceNumber +" failed recheck while seeding. Re-downloading...");
}else{
Debug.out(getDisplayName() + "Piece #" +pieceNumber +" failed recheck after being reported as bad. Re-downloading...");
}
Logger.log(new LogAlert(this, LogAlert.REPEATABLE, LogAlert.AT_ERROR,
"Download '" + getDisplayName() + "': piece " + pieceNumber
+ " has been corrupted, re-downloading"));
if ( !restart_initiated ){
restart_initiated = true;
adapter.restartDownload(true);
}
}
return;
}
// piece can be null when running a recheck on completion
// actually, give the above code I don't think this is true anymore...
final PEPieceImpl pePiece =pePieces[pieceNumber];
if ( outcome == 1 ){
// the piece has been written correctly
try{
if (pePiece !=null){
if (needsMD5CheckOnCompletion(pieceNumber)){
MD5CheckPiece(pePiece, true);
}
final List list =pePiece.getPieceWrites();
if (list.size() >0 ){
//For each Block
for (int i =0; i <pePiece.getNbBlocks(); i++ ){
//System.out.println("Processing block " + i);
//Find out the correct hash
final List listPerBlock =pePiece.getPieceWrites(i);
byte[] correctHash = null;
//PEPeer correctSender = null;
Iterator iterPerBlock = listPerBlock.iterator();
while (iterPerBlock.hasNext())
{
final PEPieceWriteImpl write =(PEPieceWriteImpl) iterPerBlock.next();
if (write.isCorrect())
{
correctHash = write.getHash();
//correctSender = write.getSender();
}
}
//System.out.println("Correct Hash " + correctHash);
//If it's found
if (correctHash !=null)
{
iterPerBlock = listPerBlock.iterator();
while (iterPerBlock.hasNext())
{
final PEPieceWriteImpl write =(PEPieceWriteImpl) iterPerBlock.next();
if (!Arrays.equals(write.getHash(), correctHash))
{
//Bad peer found here
badPeerDetected(write.getSender(),pieceNumber);
}
}
}
}
}
}
}finally{
// regardless of any possible failure above, tidy up correctly
removePiece(pePiece, pieceNumber);
// send all clients a have message
sendHave(pieceNumber); //XXX: if Done isn't set yet, might refuse to send this piece
}
}else if ( outcome == 0 ){
// the piece is corrupt
if ( pePiece != null ){
try{
MD5CheckPiece(pePiece, false);
final String[] writers =pePiece.getWriters();
final List uniqueWriters =new ArrayList();
final int[] writesPerWriter =new int[writers.length];
for (int i =0; i <writers.length; i++ )
{
final String writer =writers[i];
if (writer !=null)
{
int writerId = uniqueWriters.indexOf(writer);
if (writerId ==-1)
{
uniqueWriters.add(writer);
writerId = uniqueWriters.size() - 1;
}
writesPerWriter[writerId]++;
}
}
final int nbWriters =uniqueWriters.size();
if (nbWriters ==1)
{
//Very simple case, only 1 peer contributed for that piece,
//so, let's mark it as a bad peer
String bad_ip = (String)uniqueWriters.get(0);
PEPeerTransport bad_peer = getTransportFromAddress( bad_ip );
if ( bad_peer != null ){
bad_peer.sendBadPiece( pieceNumber );
}
badPeerDetected( bad_ip, pieceNumber);
//and let's reset the whole piece
pePiece.reset();
}else if ( nbWriters > 1 ){
int maxWrites = 0;
String bestWriter =null;
PEPeerTransport bestWriter_transport = null;
for (int i =0; i <uniqueWriters.size(); i++ ){
final int writes =writesPerWriter[i];
if (writes > maxWrites ){
final String writer =(String) uniqueWriters.get(i);
PEPeerTransport pt =getTransportFromAddress(writer);
if ( pt !=null &&
pt.getReservedPieceNumbers() == null &&
!ip_filter.isInRange(writer, getDisplayName(),getTorrentHash())){
bestWriter = writer;
maxWrites = writes;
bestWriter_transport = pt;
}
}
}
if ( bestWriter !=null ){
pePiece.setReservedBy(bestWriter);
bestWriter_transport.addReservedPieceNumber(pePiece.getPieceNumber());
pePiece.setRequestable();
for (int i =0; i <pePiece.getNbBlocks(); i++ ){
// If the block was contributed by someone else
if (writers[i] == null ||!writers[i].equals( bestWriter )){
pePiece.reDownloadBlock(i);
}
}
}else{
//In all cases, reset the piece
pePiece.reset();
}
}else{
//In all cases, reset the piece
pePiece.reset();
}
//if we are in end-game mode, we need to re-add all the piece chunks
//to the list of chunks needing to be downloaded
piecePicker.addEndGameChunks(pePiece);
_stats.hashFailed(pePiece.getLength());
}catch( Throwable e ){
Debug.printStackTrace(e);
// anything craps out in the above code, ensure we tidy up
pePiece.reset();
}
}else{
// no active piece for some reason, clear down DM piece anyway
Debug.out(getDisplayName() + "Piece #" +pieceNumber +" failed check and no active piece, resetting..." );
dm_pieces[pieceNumber].reset();
}
}else{
// cancelled, download stopped
}
}finally{
if (check_type ==CHECK_REASON_SCAN ){
rescan_piece_time = SystemTime.getCurrentTime();
}
if (!seeding_mode ){
checkFinished( false );
}
}
}
private void badPeerDetected(String ip, int piece_number )
{
boolean hash_fail = piece_number >= 0;
// note that peer can be NULL but things still work in the main
PEPeerTransport peer = getTransportFromAddress(ip);
if ( hash_fail && peer != null ){
Iterator<PEPeerManagerListener> it = peer_manager_listeners_cow.iterator();
while( it.hasNext()){
try{
it.next().peerSentBadData( this, peer, piece_number );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
// Debug.out("Bad Peer Detected: " + peerIP + " [" + peer.getClient() + "]");
IpFilterManager filter_manager =IpFilterManagerFactory.getSingleton();
//Ban fist to avoid a fast reco of the bad peer
int nbWarnings = filter_manager.getBadIps().addWarningForIp(ip);
boolean disconnect_peer = false;
// no need to reset the bad chunk count as the peer is going to be disconnected and
// if it comes back it'll start afresh
// warning limit only applies to hash-fails, discards cause immediate action
if ( nbWarnings > WARNINGS_LIMIT ){
if (COConfigurationManager.getBooleanParameter("Ip Filter Enable Banning")){
// if a block-ban occurred, check other connections
if (ip_filter.ban(ip, getDisplayName(), false )){
checkForBannedConnections();
}
// Trace the ban
if (Logger.isEnabled()){
Logger.log(new LogEvent(peer, LOGID, LogEvent.LT_ERROR, ip +" : has been banned and won't be able "
+"to connect until you restart azureus"));
}
disconnect_peer = true;
}
}else if ( !hash_fail ){
// for failures due to excessive discard we boot the peer anyway
disconnect_peer = true;
}
if ( disconnect_peer ){
if ( peer != null ){
final int ps =peer.getPeerState();
// might have been through here very recently and already started closing
// the peer (due to multiple bad blocks being found from same peer when checking piece)
if (!(ps ==PEPeer.CLOSING ||ps ==PEPeer.DISCONNECTED))
{
// Close connection
closeAndRemovePeer(peer, "has sent too many " + (hash_fail?"bad pieces":"discarded blocks") + ", " +WARNINGS_LIMIT +" max.", true);
}
}
}
}
public PEPiece[] getPieces()
{
return pePieces;
}
public PEPiece getPiece(int pieceNumber)
{
return pePieces[pieceNumber];
}
public PEPeerStats
createPeerStats(
PEPeer owner )
{
return( new PEPeerStatsImpl( owner ));
}
public DiskManagerReadRequest
createDiskManagerRequest(
int pieceNumber,
int offset,
int length )
{
return( disk_mgr.createReadRequest( pieceNumber, offset, length ));
}
public boolean
requestExists(
String peer_ip,
int piece_number,
int offset,
int length )
{
ArrayList peer_transports = peer_transports_cow;
DiskManagerReadRequest request = null;
for (int i=0; i < peer_transports.size(); i++) {
PEPeerTransport conn = (PEPeerTransport)peer_transports.get( i );
if ( conn.getIp().equals( peer_ip )){
if ( request == null ){
request = createDiskManagerRequest( piece_number, offset, length );
}
if ( conn.getRequestIndex( request ) != -1 ){
return( true );
}
}
}
return( false );
}
public boolean
seedPieceRecheck()
{
if ( !( enable_seeding_piece_rechecks || isSeeding())){
return( false );
}
int max_reads = 0;
int max_reads_index = 0;
if ( TEST_PERIODIC_SEEDING_SCAN_FAIL_HANDLING ){
max_reads_index = (int)(Math.random()*dm_pieces.length);;
max_reads = dm_pieces[ max_reads_index ].getNbBlocks()*3;
}else{
for (int i=0;i<dm_pieces.length;i++){
// skip dnd pieces
DiskManagerPiece dm_piece = dm_pieces[i];
if ( !dm_piece.isDone()){
continue;
}
int num = dm_piece.getReadCount()&0xffff;
if ( num > SEED_CHECK_WAIT_MARKER ){
// recently been checked, skip for a while
num--;
if ( num == SEED_CHECK_WAIT_MARKER ){
num = 0;
}
dm_piece.setReadCount((short)num);
}else{
if ( num > max_reads ){
max_reads = num;
max_reads_index = i;
}
}
}
}
if ( max_reads > 0 ){
DiskManagerPiece dm_piece = dm_pieces[ max_reads_index ];
// if the piece has been read 3 times (well, assuming each block is read once,
// which is obviously wrong, but...)
if ( max_reads >= dm_piece.getNbBlocks() * 3 ){
DiskManagerCheckRequest req = disk_mgr.createCheckRequest( max_reads_index, new Integer( CHECK_REASON_SEEDING_CHECK ) );
req.setAdHoc( true );
req.setLowPriority( true );
if (Logger.isEnabled())
Logger.log(new LogEvent(disk_mgr.getTorrent(), LOGID,
"Rechecking piece " + max_reads_index + " while seeding as most active"));
disk_mgr.enqueueCheckRequest( req, this );
dm_piece.setReadCount((short)65535);
// clear out existing, non delayed pieces so we start counting piece activity
// again
for (int i=0;i<dm_pieces.length;i++){
if ( i != max_reads_index ){
int num = dm_pieces[i].getReadCount()&0xffff;
if ( num < SEED_CHECK_WAIT_MARKER ){
dm_pieces[i].setReadCount((short)0 );
}
}
}
return( true );
}
}
return( false );
}
public void
addListener(
PEPeerManagerListener l )
{
try{
this_mon.enter();
//copy on write
final ArrayList peer_manager_listeners = new ArrayList( peer_manager_listeners_cow.size() + 1 );
peer_manager_listeners.addAll( peer_manager_listeners_cow );
peer_manager_listeners.add( l );
peer_manager_listeners_cow = peer_manager_listeners;
}finally{
this_mon.exit();
}
}
public void
removeListener(
PEPeerManagerListener l )
{
try{
this_mon.enter();
//copy on write
final ArrayList peer_manager_listeners = new ArrayList( peer_manager_listeners_cow );
peer_manager_listeners.remove( l );
peer_manager_listeners_cow = peer_manager_listeners;
}finally{
this_mon.exit();
}
}
public void
parameterChanged(
String parameterName)
{
if ( parameterName.equals("Ip Filter Enabled")){
checkForBannedConnections();
}
}
private void
checkForBannedConnections()
{
if ( ip_filter.isEnabled()){ //if ipfiltering is enabled, remove any existing filtered connections
ArrayList to_close = null;
final ArrayList peer_transports = peer_transports_cow;
String name = getDisplayName();
byte[] hash = getTorrentHash();
for (int i=0; i < peer_transports.size(); i++) {
final PEPeerTransport conn = (PEPeerTransport)peer_transports.get( i );
if ( ip_filter.isInRange( conn.getIp(), name, hash )) {
if( to_close == null ) to_close = new ArrayList();
to_close.add( conn );
}
}
if( to_close != null ) {
for( int i=0; i < to_close.size(); i++ ) {
closeAndRemovePeer( (PEPeerTransport)to_close.get(i), "IPFilter banned IP address", true );
}
}
}
}
public boolean
isSeeding()
{
return( seeding_mode );
}
public boolean isInEndGameMode() {
return piecePicker.isInEndGameMode();
}
public boolean isSuperSeedMode() {
return superSeedMode;
}
public boolean
canToggleSuperSeedMode()
{
if ( superSeedMode ){
return( true );
}
return( superSeedPieces == null && this.getRemaining() ==0 );
}
public void
setSuperSeedMode(
boolean _superSeedMode)
{
if ( _superSeedMode == superSeedMode ){
return;
}
boolean kick_peers = false;
if ( _superSeedMode ){
if ( superSeedPieces == null && this.getRemaining() ==0 ){
superSeedMode = true;
initialiseSuperSeedMode();
kick_peers = true;
}
}else{
superSeedMode = false;
kick_peers = true;
}
if ( kick_peers ){
// turning on/off super-seeding, gotta kick all connected peers so they get the
// "right" bitfield
ArrayList peer_transports = peer_transports_cow;
for (int i=0; i < peer_transports.size(); i++) {
PEPeerTransport conn = (PEPeerTransport)peer_transports.get( i );
closeAndRemovePeer( conn, "Turning on super-seeding", false );
}
}
}
private void
initialiseSuperSeedMode()
{
superSeedPieces = new SuperSeedPiece[_nbPieces];
for(int i = 0 ; i < _nbPieces ; i++) {
superSeedPieces[i] = new SuperSeedPiece(this,i);
}
}
private void updatePeersInSuperSeedMode() {
if(!superSeedMode) {
return;
}
//Refresh the update time in case this is needed
for(int i = 0 ; i < superSeedPieces.length ; i++) {
superSeedPieces[i].updateTime();
}
//Use the same number of announces than unchoke
int nbUnchoke = adapter.getMaxUploads();
if(superSeedModeNumberOfAnnounces >= 2 * nbUnchoke)
return;
//Find an available Peer
PEPeer selectedPeer = null;
List sortedPeers = null;
final ArrayList peer_transports = peer_transports_cow;
sortedPeers = new ArrayList(peer_transports.size());
Iterator iter = peer_transports.iterator();
while(iter.hasNext()) {
sortedPeers.add(new SuperSeedPeer((PEPeer)iter.next()));
}
Collections.sort(sortedPeers);
iter = sortedPeers.iterator();
while(iter.hasNext()) {
final PEPeer peer = ((SuperSeedPeer)iter.next()).peer;
if((peer.getUniqueAnnounce() == -1) && (peer.getPeerState() == PEPeer.TRANSFERING)) {
selectedPeer = peer;
break;
}
}
if(selectedPeer == null ||selectedPeer.getPeerState() >=PEPeer.CLOSING)
return;
if(selectedPeer.getUploadHint() == 0) {
//Set to infinite
selectedPeer.setUploadHint(Constants.CRAPPY_INFINITY_AS_INT);
}
//Find a piece
boolean found = false;
SuperSeedPiece piece = null;
boolean loopdone = false; // add loop status
while(!found) {
piece = superSeedPieces[superSeedModeCurrentPiece];
if(piece.getLevel() > 0) {
piece = null;
superSeedModeCurrentPiece++;
if(superSeedModeCurrentPiece >= _nbPieces) {
superSeedModeCurrentPiece = 0;
if (loopdone) { // if already been here, has been full loop through pieces, quit
//quit superseed mode
superSeedMode = false;
closeAndRemoveAllPeers( "quiting SuperSeed mode", true );
return;
}
else {
// loopdone==false --> first time here --> go through the pieces
// for a second time to check if reserved pieces have got freed due to peers leaving
loopdone = true;
}
}
} else {
found = true;
}
}
if(piece == null) {
return;
}
//If this peer already has this piece, return (shouldn't happen)
if(selectedPeer.isPieceAvailable(piece.getPieceNumber())) {
return;
}
selectedPeer.setUniqueAnnounce(piece.getPieceNumber());
superSeedModeNumberOfAnnounces++;
piece.pieceRevealedToPeer();
((PEPeerTransport)selectedPeer).sendHave(piece.getPieceNumber());
}
public void updateSuperSeedPiece(PEPeer peer,int pieceNumber) {
// currently this gets only called from bitfield scan function in PEPeerTransportProtocol
if (!superSeedMode)
return;
superSeedPieces[pieceNumber].peerHasPiece(null);
if(peer.getUniqueAnnounce() == pieceNumber)
{
peer.setUniqueAnnounce(-1);
superSeedModeNumberOfAnnounces--;
}
}
public boolean
isExtendedMessagingEnabled()
{
return( adapter.isExtendedMessagingEnabled());
}
public boolean
isPeerExchangeEnabled()
{
return( adapter.isPeerExchangeEnabled());
}
public LimitedRateGroup getUploadLimitedRateGroup() { return upload_limited_rate_group; }
public LimitedRateGroup getDownloadLimitedRateGroup() { return download_limited_rate_group; }
/** To retreive arbitrary objects against this object. */
public Object
getData(
String key)
{
try{
this_mon.enter();
if (user_data == null) return null;
return user_data.get(key);
}finally{
this_mon.exit();
}
}
/** To store arbitrary objects against a control. */
public void
setData(
String key,
Object value)
{
try{
this_mon.enter();
if (user_data == null) {
user_data = new HashMap();
}
if (value == null) {
if (user_data.containsKey(key))
user_data.remove(key);
} else {
user_data.put(key, value);
}
}finally{
this_mon.exit();
}
}
public int
getConnectTimeout(
int ct_def )
{
if ( ct_def <= 0 ){
return( ct_def );
}
if ( seeding_mode ){
// seeding mode connections are already de-prioritised so nothing to do
return( ct_def );
}
int max_sim_con = TCPConnectionManager.MAX_SIMULTANIOUS_CONNECT_ATTEMPTS;
// high, let's not mess with things
if ( max_sim_con >= 50 ){
return( ct_def );
}
// we have somewhat limited outbound connection limits, see if it makes sense to
// reduce the connect timeout to prevent connection stall due to a bunch getting
// stuck 'connecting' for a long time and stalling us
int connected = _seeds + _peers;
int connecting = _tcpConnectingConnections;
int queued = _tcpPendingConnections;
int not_yet_connected = peer_database.getDiscoveredPeerCount();
int max = getMaxConnections();
int potential = connecting + queued + not_yet_connected;
/*
System.out.println(
"connected=" + connected +
", queued=" + queued +
", connecting=" + connecting +
", queued=" + queued +
", not_yet=" + not_yet_connected +
", max=" + max );
*/
// not many peers -> don't amend
int lower_limit = max/4;
if ( potential <= lower_limit || max == lower_limit ){
return( ct_def );
}
// if we got lots of potential, use minimum delay
final int MIN_CT = 7500;
if ( potential >= max ){
return( MIN_CT );
}
// scale between MIN and ct_def
int pos = potential - lower_limit;
int scale = max - lower_limit;
int res = MIN_CT + ( ct_def - MIN_CT )*(scale - pos )/scale;
// System.out.println( "scaled->" + res );
return( res );
}
private void doConnectionChecks()
{
//every 1 second
if ( mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL == 0 ){
final ArrayList peer_transports = peer_transports_cow;
int num_waiting_establishments = 0;
int udp_connections = 0;
for( int i=0; i < peer_transports.size(); i++ ) {
final PEPeerTransport transport = (PEPeerTransport)peer_transports.get( i );
//update waiting count
final int state = transport.getConnectionState();
if( state == PEPeerTransport.CONNECTION_PENDING || state == PEPeerTransport.CONNECTION_CONNECTING ) {
num_waiting_establishments++;
}
if ( !transport.isTCP()){
udp_connections++;
}
}
int allowed_seeds = getMaxSeedConnections();
if ( allowed_seeds > 0 ){
int to_disconnect = _seeds - allowed_seeds;
if ( to_disconnect > 0 ){
// seeds are limited by people trying to get a reasonable upload by connecting
// to leechers where possible. disconnect seeds from end of list to prevent
// cycling of seeds
for( int i=peer_transports.size()-1; i >= 0 && to_disconnect > 0; i-- ){
final PEPeerTransport transport = (PEPeerTransport)peer_transports.get( i );
if ( transport.isSeed()){
closeAndRemovePeer( transport, "Too many seeds", false );
to_disconnect--;
}
}
}
}
//pass from storage to connector
int allowed = getMaxNewConnectionsAllowed();
if( allowed < 0 || allowed > 1000 ) allowed = 1000; //ensure a very upper limit so it doesnt get out of control when using PEX
if( adapter.isNATHealthy()) { //if unfirewalled, leave slots avail for remote connections
final int free = getMaxConnections() / 20; //leave 5%
allowed = allowed - free;
}
if( allowed > 0 ) {
//try and connect only as many as necessary
final int wanted = TCPConnectionManager.MAX_SIMULTANIOUS_CONNECT_ATTEMPTS - num_waiting_establishments;
if( wanted > allowed ) {
num_waiting_establishments += wanted - allowed;
}
int remaining = allowed;
int tcp_remaining = TCPNetworkManager.getSingleton().getConnectDisconnectManager().getMaxOutboundPermitted();
int udp_remaining = UDPNetworkManager.getSingleton().getConnectionManager().getMaxOutboundPermitted();
//load stored peer-infos to be established
while( num_waiting_establishments < TCPConnectionManager.MAX_SIMULTANIOUS_CONNECT_ATTEMPTS && ( tcp_remaining > 0 || udp_remaining > 0 )){
if( !is_running ) break;
final PeerItem item = peer_database.getNextOptimisticConnectPeer();
if( item == null || !is_running ) break;
final PeerItem self = peer_database.getSelfPeer();
if( self != null && self.equals( item ) ) {
continue;
}
if( !isAlreadyConnected( item ) ) {
final String source = PeerItem.convertSourceString( item.getSource() );
final boolean use_crypto = item.getHandshakeType() == PeerItemFactory.HANDSHAKE_TYPE_CRYPTO;
int tcp_port = item.getTCPPort();
int udp_port = item.getUDPPort();
if ( udp_port == 0 && udp_probe_enabled ){
// for probing we assume udp port same as tcp
udp_port = tcp_port;
}
boolean prefer_udp_overall = prefer_udp || prefer_udp_default;
if ( prefer_udp_overall && udp_port == 0 ){
// see if we have previous record of this address as udp connectable
byte[] address = item.getIP().getBytes();
BloomFilter bloom = prefer_udp_bloom;
if ( bloom != null && bloom.contains( address )){
udp_port = tcp_port;
}
}
boolean tcp_ok = TCPNetworkManager.TCP_OUTGOING_ENABLED && tcp_port > 0 && tcp_remaining > 0;
boolean udp_ok = UDPNetworkManager.UDP_OUTGOING_ENABLED && udp_port > 0 && udp_remaining > 0;
if ( tcp_ok && !( prefer_udp_overall && udp_ok )){
if ( makeNewOutgoingConnection( source, item.getAddressString(), tcp_port, udp_port, true, use_crypto, item.getCryptoLevel(), null) == null) {
tcp_remaining--;
num_waiting_establishments++;
remaining--;
}
}else if ( udp_ok ){
if ( makeNewOutgoingConnection( source, item.getAddressString(), tcp_port, udp_port, false, use_crypto, item.getCryptoLevel(), null) == null) {
udp_remaining--;
num_waiting_establishments++;
remaining--;
}
}
}
}
if ( UDPNetworkManager.UDP_OUTGOING_ENABLED &&
remaining > 0 &&
udp_remaining > 0 &&
udp_connections < MAX_UDP_CONNECTIONS ){
doUDPConnectionChecks( remaining );
}
}
}
//every 5 seconds
if ( mainloop_loop_count % MAINLOOP_FIVE_SECOND_INTERVAL == 0 ) {
final ArrayList peer_transports = peer_transports_cow;
for( int i=0; i < peer_transports.size(); i++ ) {
final PEPeerTransport transport = (PEPeerTransport)peer_transports.get( i );
//check for timeouts
if( transport.doTimeoutChecks() ) continue;
//keep-alive check
transport.doKeepAliveCheck();
//speed tuning check
transport.doPerformanceTuningCheck();
}
}
// every 10 seconds check for connected + banned peers
if ( mainloop_loop_count % MAINLOOP_TEN_SECOND_INTERVAL == 0 )
{
final long last_update = ip_filter.getLastUpdateTime();
if ( last_update != ip_filter_last_update_time )
{
ip_filter_last_update_time = last_update;
checkForBannedConnections();
}
}
//every 30 seconds
if ( mainloop_loop_count % MAINLOOP_THIRTY_SECOND_INTERVAL == 0 ) {
//if we're at our connection limit, time out the least-useful
//one so we can establish a possibly-better new connection
optimisticDisconnectCount = 0;
if( getMaxNewConnectionsAllowed() == 0 ) { //we've reached limit
doOptimisticDisconnect( false, false );
}
}
//sweep over all peers in a 60 second timespan
float percentage = ((mainloop_loop_count % MAINLOOP_SIXTY_SECOND_INTERVAL) + 1F) / (1F *MAINLOOP_SIXTY_SECOND_INTERVAL);
int goal;
if(mainloop_loop_count % MAINLOOP_SIXTY_SECOND_INTERVAL == 0)
{
goal = 0;
sweepList = peer_transports_cow;
} else
goal = (int)Math.floor(percentage * sweepList.size());
for( int i=nextPEXSweepIndex; i < goal && i < sweepList.size(); i++) {
//System.out.println(mainloop_loop_count+" %:"+percentage+" start:"+nextPEXSweepIndex+" current:"+i+" <"+goal+"/"+sweepList.size());
final PEPeerTransport peer = (PEPeerTransport)sweepList.get( i );
peer.updatePeerExchange();
}
nextPEXSweepIndex = goal;
}
private void
doUDPConnectionChecks(
int number )
{
List new_connections = null;
try{
peer_transports_mon.enter();
long now = SystemTime.getCurrentTime();
if ( udp_reconnects.size() > 0 && now - last_udp_reconnect >= UDP_RECONNECT_MIN_MILLIS ){
last_udp_reconnect = now;
Iterator it = udp_reconnects.values().iterator();
PEPeerTransport peer = (PEPeerTransport)it.next();
it.remove();
if (Logger.isEnabled()){
Logger.log(new LogEvent(this, LOGID, LogEvent.LT_INFORMATION, "Reconnecting to previous failed peer " + peer.getPeerItemIdentity().getAddressString()));
}
if ( new_connections == null ){
new_connections = new ArrayList();
}
new_connections.add( peer );
number--;
if ( number <= 0 ){
return;
}
}
if ( pending_nat_traversals.size() == 0 ){
return;
}
int max = MAX_UDP_TRAVERSAL_COUNT;
// bigger the swarm, less chance of doing it
if ( seeding_mode ){
if ( _peers > 8 ){
max = 0;
}else{
max = 1;
}
}else if ( _seeds > 8 ){
max = 0;
}else if ( _seeds > 4 ){
max = 1;
}
int avail = max - udp_traversal_count;
int to_do = Math.min( number, avail );
Iterator it = pending_nat_traversals.values().iterator();
while( to_do > 0 && it.hasNext()){
to_do--;
final PEPeerTransport peer = (PEPeerTransport)it.next();
it.remove();
PeerNATTraverser.getSingleton().create(
this,
new InetSocketAddress( peer.getPeerItemIdentity().getAddressString(), peer.getPeerItemIdentity().getUDPPort() ),
new PeerNATTraversalAdapter()
{
private boolean done;
public void
success(
InetSocketAddress target )
{
complete();
PEPeerTransport newTransport = peer.reconnect(true, false);
if( newTransport != null ){
newTransport.setData(PEER_NAT_TRAVERSE_DONE_KEY, "");
}
}
public void
failed()
{
complete();
}
protected void
complete()
{
try{
peer_transports_mon.enter();
if ( !done ){
done = true;
udp_traversal_count--;
}
}finally{
peer_transports_mon.exit();
}
}
});
udp_traversal_count++;
}
}finally{
peer_transports_mon.exit();
if ( new_connections != null ){
for (int i=0;i<new_connections.size();i++){
PEPeerTransport peer_item = (PEPeerTransport)new_connections.get(i);
// don't call when holding monitor - deadlock potential
peer_item.reconnect(true, false);
}
}
}
}
// counter is reset every 30s by doConnectionChecks()
private int optimisticDisconnectCount = 0;
public boolean doOptimisticDisconnect( boolean pending_lan_local_peer, boolean force )
{
final ArrayList peer_transports = peer_transports_cow;
PEPeerTransport max_transport = null;
PEPeerTransport max_seed_transport = null;
PEPeerTransport max_non_lan_transport = null;
long max_time = 0;
long max_seed_time = 0;
long max_non_lan_time = 0;
List<Long> activeConnectionTimes = new ArrayList<Long>(peer_transports.size());
int lan_peer_count = 0;
for( int i=0; i < peer_transports.size(); i++ ) {
final PEPeerTransport peer = (PEPeerTransport)peer_transports.get( i );
if( peer.getConnectionState() == PEPeerTransport.CONNECTION_FULLY_ESTABLISHED ) {
final long timeSinceConnection =peer.getTimeSinceConnectionEstablished();
final long timeSinceSentData =peer.getTimeSinceLastDataMessageSent();
activeConnectionTimes.add(timeSinceConnection);
long peerTestTime = 0;
if( seeding_mode){
if( timeSinceSentData != -1 )
peerTestTime = timeSinceSentData; //ensure we've sent them at least one data message to qualify for drop
}else{
final long timeSinceGoodData =peer.getTimeSinceGoodDataReceived();
if( timeSinceGoodData == -1 )
peerTestTime +=timeSinceConnection; //never received
else
peerTestTime +=timeSinceGoodData;
// try to drop unInteresting in favor of Interesting connections
if (!peer.isInteresting())
{
if (!peer.isInterested()) // if mutually unInterested, really try to drop the connection
peerTestTime +=timeSinceConnection +timeSinceSentData; // if we never sent, it will subtract 1, which is good
else
peerTestTime +=(timeSinceConnection -timeSinceSentData); // try to give interested peers a chance to get data
peerTestTime *=2;
}
peerTestTime +=peer.getSnubbedTime();
}
if( !peer.isIncoming() ){
peerTestTime = peerTestTime * 2; //prefer to drop a local connection, to make room for more remotes
}
if ( peer.isLANLocal()){
lan_peer_count++;
}else{
if( peerTestTime > max_non_lan_time ) {
max_non_lan_time = peerTestTime;
max_non_lan_transport = peer;
}
}
// anti-leech checks
if( !seeding_mode ) {
// remove long-term snubbed peers with higher probability
peerTestTime += peer.getSnubbedTime();
if(peer.getSnubbedTime() > 2*60) {peerTestTime*=1.5;}
PEPeerStats pestats = peer.getStats();
// everybody has deserverd a chance of half an MB transferred data
if(pestats.getTotalDataBytesReceived()+pestats.getTotalDataBytesSent() > 1024*512 ) {
boolean goodPeer = true;
// we don't like snubbed peers with a negative gain
if( peer.isSnubbed() && pestats.getTotalDataBytesReceived() < pestats.getTotalDataBytesSent() ) {
peerTestTime *= 1.5;
goodPeer = false;
}
// we don't like peers with a very bad ratio (10:1)
if( pestats.getTotalDataBytesSent() > pestats.getTotalDataBytesReceived() * 10 ) {
peerTestTime *= 2;
goodPeer = false;
}
// modify based on discarded : overall downloaded ratio
if( pestats.getTotalDataBytesReceived() > 0 && pestats.getTotalBytesDiscarded() > 0 ) {
peerTestTime = (long)(peerTestTime *( 1.0+((double)pestats.getTotalBytesDiscarded()/(double)pestats.getTotalDataBytesReceived())));
}
// prefer peers that do some work, let the churn happen with peers that did nothing
if(goodPeer)
peerTestTime *= 0.7;
}
}
if( peerTestTime > max_time ) {
max_time = peerTestTime;
max_transport = peer;
}
if ( peer.isSeed() || peer.isRelativeSeed()){
if( peerTestTime > max_seed_time ) {
max_seed_time = peerTestTime;
max_seed_transport = peer;
}
}
}
}
long medianConnectionTime;
if ( activeConnectionTimes.size() > 0 ){
Collections.sort(activeConnectionTimes);
medianConnectionTime = activeConnectionTimes.get(activeConnectionTimes.size()/2);
}else{
medianConnectionTime = 0;
}
// allow 1 disconnect every 30s per 30 peers; 2 at least every 30s
int maxOptimistics = Math.max(getMaxConnections()/30,2);
// avoid unnecessary churn, e.g.
if(!pending_lan_local_peer && !force && optimisticDisconnectCount >= maxOptimistics && medianConnectionTime < 5*60*1000)
return false;
// don't boot lan peers if we can help it (unless we have a few of them)
if ( max_transport != null ){
final int LAN_PEER_MAX = 4;
if ( max_transport.isLANLocal() && lan_peer_count < LAN_PEER_MAX && max_non_lan_transport != null ){
// override lan local max with non-lan local max
max_transport = max_non_lan_transport;
max_time = max_non_lan_time;
}
// if we have a seed limit, kick seeds in preference to non-seeds
if( getMaxSeedConnections() > 0 && max_seed_transport != null && max_time > 5*60*1000 ) {
closeAndRemovePeer( max_seed_transport, "timed out by doOptimisticDisconnect()", true );
optimisticDisconnectCount++;
return true;
}
if( max_transport != null && max_time > 5 *60*1000 ) { //ensure a 5 min minimum test time
closeAndRemovePeer( max_transport, "timed out by doOptimisticDisconnect()", true );
optimisticDisconnectCount++;
return true;
}
// kick worst peers to accomodate lan peer
if ( pending_lan_local_peer && lan_peer_count < LAN_PEER_MAX ){
closeAndRemovePeer( max_transport, "making space for LAN peer in doOptimisticDisconnect()", true );
optimisticDisconnectCount++;
return true;
}
if ( force ){
closeAndRemovePeer( max_transport, "force removal of worst peer in doOptimisticDisconnect()", true );
return true;
}
}else if ( force ){
if ( peer_transports.size() > 0 ){
PEPeerTransport pt = (PEPeerTransport)peer_transports.get( new Random().nextInt( peer_transports.size()));
closeAndRemovePeer( pt, "force removal of random peer in doOptimisticDisconnect()", true );
return true;
}
}
return false;
}
public PeerExchangerItem createPeerExchangeConnection( final PEPeerTransport base_peer ) {
if( base_peer.getTCPListenPort() > 0 ) { //only accept peers whose remote port is known
final PeerItem peer =
PeerItemFactory.createPeerItem( base_peer.getIp(),
base_peer.getTCPListenPort(),
PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE,
base_peer.getPeerItemIdentity().getHandshakeType(),
base_peer.getUDPListenPort(),
PeerItemFactory.CRYPTO_LEVEL_1,
0 );
return peer_database.registerPeerConnection( peer, new PeerExchangerItem.Helper(){
public boolean
isSeed()
{
return base_peer.isSeed();
}
});
}
return null;
}
private boolean isAlreadyConnected( PeerItem peer_id ) {
final ArrayList peer_transports = peer_transports_cow;
for( int i=0; i < peer_transports.size(); i++ ) {
final PEPeerTransport peer = (PEPeerTransport)peer_transports.get( i );
if( peer.getPeerItemIdentity().equals( peer_id ) ) return true;
}
return false;
}
public void peerVerifiedAsSelf( PEPeerTransport self ) {
if( self.getTCPListenPort() > 0 ) { //only accept self if remote port is known
final PeerItem peer = PeerItemFactory.createPeerItem( self.getIp(), self.getTCPListenPort(),
PeerItem.convertSourceID( self.getPeerSource() ), self.getPeerItemIdentity().getHandshakeType(), self.getUDPListenPort(),PeerItemFactory.CRYPTO_LEVEL_CURRENT, 0 );
peer_database.setSelfPeer( peer );
}
}
public boolean
canIPBeBanned(
String ip )
{
return true;
}
public boolean
canIPBeBlocked(
String ip,
byte[] torrent_hash )
{
return true;
}
public long
getHiddenBytes()
{
if ( hidden_piece < 0 ){
return( 0 );
}
return( dm_pieces[hidden_piece].getLength());
}
public int
getHiddenPiece()
{
return( hidden_piece );
}
public void IPBlockedListChanged(IpFilter filter) {
Iterator it = peer_transports_cow.iterator();
String name = getDisplayName();
byte[] hash = getTorrentHash();
while( it.hasNext()){
try {
PEPeerTransport peer = (PEPeerTransport)it.next();
if (filter.isInRange(peer.getIp(), name, hash )) {
peer.closeConnection( "IP address blocked by filters" );
}
} catch (Exception e) {
}
}
}
public void IPBanned(BannedIp ip)
{
for (int i =0; i <_nbPieces; i++ )
{
if (pePieces[i] !=null)
pePieces[i].reDownloadBlocks(ip.getIp());
}
}
public int getAverageCompletionInThousandNotation()
{
final ArrayList peer_transports = peer_transports_cow;
if (peer_transports !=null)
{
final long total =disk_mgr.getTotalLength();
final int my_completion = total == 0
? 1000
: (int) ((1000 * (total - disk_mgr.getRemainingExcludingDND())) / total);
int sum = my_completion == 1000 ? 0 : my_completion; //add in our own percentage if not seeding
int num = my_completion == 1000 ? 0 : 1;
for (int i =0; i <peer_transports.size(); i++ )
{
final PEPeer peer =(PEPeer) peer_transports.get(i);
if (peer.getPeerState() ==PEPeer.TRANSFERING &&!peer.isSeed())
{
num++;
sum += peer.getPercentDoneInThousandNotation();
}
}
return num > 0 ? sum / num : 0;
}
return -1;
}
public int
getMaxConnections()
{
return( adapter.getMaxConnections());
}
public int
getMaxSeedConnections()
{
return( adapter.getMaxSeedConnections());
}
public int
getMaxNewConnectionsAllowed()
{
final int dl_max = getMaxConnections();
final int allowed_peers = PeerUtils.numNewConnectionsAllowed(getPeerIdentityDataID(), dl_max );
return( allowed_peers );
}
public int getSchedulePriority() {
return isSeeding() ? Integer.MAX_VALUE : adapter.getPosition();
}
public boolean
hasPotentialConnections()
{
return( pending_nat_traversals.size() + peer_database.getDiscoveredPeerCount() > 0 );
}
public String
getRelationText()
{
return( adapter.getLogRelation().getRelationText());
}
public Object[]
getQueryableInterfaces()
{
return( adapter.getLogRelation().getQueryableInterfaces());
}
public PEPeerTransport getTransportFromIdentity( byte[] peer_id ) {
final ArrayList peer_transports = peer_transports_cow;
for( int i=0; i < peer_transports.size(); i++ ) {
final PEPeerTransport conn = (PEPeerTransport)peer_transports.get( i );
if( Arrays.equals( peer_id, conn.getId() ) ) return conn;
}
return null;
}
/* peer item is not reliable for general use
public PEPeerTransport getTransportFromPeerItem(PeerItem peerItem)
{
ArrayList peer_transports =peer_transports_cow;
for (int i =0; i <peer_transports.size(); i++)
{
PEPeerTransport pt =(PEPeerTransport) peer_transports.get(i);
if (pt.getPeerItemIdentity().equals(peerItem))
return pt;
}
return null;
}
*/
public PEPeerTransport getTransportFromAddress(String peer)
{
final ArrayList peer_transports =peer_transports_cow;
for (int i =0; i <peer_transports.size(); i++)
{
final PEPeerTransport pt =(PEPeerTransport) peer_transports.get(i);
if (peer.equals(pt.getIp()))
return pt;
}
return null;
}
// Snubbed peers accounting
public void incNbPeersSnubbed()
{
nbPeersSnubbed++;
}
public void decNbPeersSnubbed()
{
nbPeersSnubbed--;
}
public void setNbPeersSnubbed(int n)
{
nbPeersSnubbed =n;
}
public int getNbPeersSnubbed()
{
return nbPeersSnubbed;
}
public boolean
getPreferUDP()
{
return( prefer_udp );
}
public void
setPreferUDP(
boolean prefer )
{
prefer_udp = prefer;
}
public boolean
isPeerSourceEnabled(
String peer_source )
{
return( adapter.isPeerSourceEnabled( peer_source ));
}
public void
peerDiscovered(
PEPeerTransport finder,
PeerItem pi )
{
final ArrayList peer_manager_listeners = peer_manager_listeners_cow;
for( int i=0; i < peer_manager_listeners.size(); i++ ) {
try{
((PEPeerManagerListener)peer_manager_listeners.get(i)).peerDiscovered( this, pi, finder );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
public TrackerPeerSource
getTrackerPeerSource()
{
return(
new TrackerPeerSourceAdapter()
{
public int
getType()
{
return( TP_PEX );
}
public int
getStatus()
{
return( isPeerExchangeEnabled()?ST_ONLINE:ST_DISABLED );
}
public String
getName()
{
return(
MessageText.getString( "tps.pex.details",
new String[]{
String.valueOf( peer_transports_cow.size()),
String.valueOf( peer_database.getExchangedPeerCount()),
String.valueOf( peer_database.getDiscoveredPeerCount())}));
}
public int
getPeers()
{
return( peer_database.getExchangedPeersUsed());
}
});
}
public void
generateEvidence(
IndentWriter writer )
{
writer.println( "PeerManager: seeding=" + seeding_mode );
writer.println(
" udp_fb=" + pending_nat_traversals.size() +
",udp_tc=" + udp_traversal_count +
",pd=[" + peer_database.getString() + "]");
String pending_udp = "";
try{
peer_transports_mon.enter();
Iterator it = pending_nat_traversals.values().iterator();
while( it.hasNext()){
PEPeerTransport peer = (PEPeerTransport)it.next();
pending_udp += (pending_udp.length()==0?"":",") + peer.getPeerItemIdentity().getAddressString() + ":" + peer.getPeerItemIdentity().getUDPPort();
}
}finally{
peer_transports_mon.exit();
}
if ( pending_udp.length() > 0 ){
writer.println( " pending_udp=" + pending_udp );
}
List traversals = PeerNATTraverser.getSingleton().getTraversals( this );
String active_udp = "";
Iterator it = traversals.iterator();
while( it.hasNext()){
InetSocketAddress ad = (InetSocketAddress)it.next();
active_udp += (active_udp.length()==0?"":",") + ad.getAddress().getHostAddress() + ":" + ad.getPort();
}
if ( active_udp.length() > 0 ){
writer.println( " active_udp=" + active_udp );
}
if ( !seeding_mode ){
writer.println( " Active Pieces" );
int num_active = 0;
try{
writer.indent();
String str = "";
int num = 0;
for (int i=0;i<pePieces.length;i++){
PEPiece piece = pePieces[i];
if ( piece != null ){
num_active++;
str += (str.length()==0?"":",") + "#" + i + " " + dm_pieces[i].getString() + ": " + piece.getString();
num++;
if ( num == 20 ){
writer.println( str );
str = "";
num = 0;
}
}
}
if ( num > 0 ){
writer.println(str);
}
}finally{
writer.exdent();
}
if ( num_active == 0 ){
writer.println( " Inactive Pieces (excluding done/skipped)" );
try{
writer.indent();
String str = "";
int num = 0;
for (int i=0;i<dm_pieces.length;i++){
DiskManagerPiece dm_piece = dm_pieces[i];
if ( dm_piece.isInteresting()){
str += (str.length()==0?"":",") + "#" + i + " " + dm_pieces[i].getString();
num++;
if ( num == 20 ){
writer.println( str );
str = "";
num = 0;
}
}
}
if ( num > 0 ){
writer.println(str);
}
}finally{
writer.exdent();
}
}
piecePicker.generateEvidence( writer );
}
try{
peer_transports_mon.enter();
writer.println( "Peers: total = " + peer_transports_cow.size());
writer.indent();
try{
writer.indent();
it = peer_transports_cow.iterator();
while( it.hasNext()){
PEPeerTransport peer = (PEPeerTransport)it.next();
peer.generateEvidence( writer );
}
}finally{
writer.exdent();
}
}finally{
peer_transports_mon.exit();
writer.exdent();
}
disk_mgr.generateEvidence( writer );
}
}