/*
* File : PEPeerTransportProtocol.java
* Created : 22-Oct-2003
* By : stuff
*
* Azureus - a Java Bittorrent client
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details ( see the LICENSE file ).
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.gudy.azureus2.core3.peer.impl.transport;
import java.net.InetAddress;
import java.net.InetSocketAddress;
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.DiskManager;
import org.gudy.azureus2.core3.disk.DiskManagerPiece;
import org.gudy.azureus2.core3.disk.DiskManagerReadRequest;
import org.gudy.azureus2.core3.logging.*;
import org.gudy.azureus2.core3.peer.*;
import org.gudy.azureus2.core3.peer.impl.PEPeerControl;
import org.gudy.azureus2.core3.peer.impl.PEPeerTransport;
import org.gudy.azureus2.core3.peer.impl.PEPeerTransportFactory;
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.util.*;
import org.gudy.azureus2.plugins.dht.mainline.MainlineDHTProvider;
import org.gudy.azureus2.plugins.network.Connection;
import org.gudy.azureus2.plugins.peers.Peer;
import org.gudy.azureus2.pluginsimpl.local.network.ConnectionImpl;
import com.aelitis.azureus.core.impl.AzureusCoreImpl;
import com.aelitis.azureus.core.networkmanager.*;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin;
import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPNetworkManager;
import com.aelitis.azureus.core.networkmanager.impl.udp.UDPNetworkManager;
import com.aelitis.azureus.core.peermanager.messaging.Message;
import com.aelitis.azureus.core.peermanager.messaging.MessageManager;
import com.aelitis.azureus.core.peermanager.messaging.azureus.*;
import com.aelitis.azureus.core.peermanager.messaging.bittorrent.*;
import com.aelitis.azureus.core.peermanager.messaging.bittorrent.ltep.*;
import com.aelitis.azureus.core.peermanager.peerdb.PeerExchangerItem;
import com.aelitis.azureus.core.peermanager.peerdb.PeerItem;
import com.aelitis.azureus.core.peermanager.peerdb.PeerItemFactory;
import com.aelitis.azureus.core.peermanager.piecepicker.PiecePicker;
import com.aelitis.azureus.core.peermanager.piecepicker.util.BitFlags;
import com.aelitis.azureus.core.peermanager.utils.*;
public class
PEPeerTransportProtocol
extends LogRelation
implements PEPeerTransport
{
protected final static LogIDs LOGID = LogIDs.PEER;
private volatile int _lastPiece =-1; //last piece that was requested from this peer (mostly to try to request from same one)
protected final PEPeerControl manager;
protected final DiskManager diskManager;
protected final PiecePicker piecePicker;
protected final int nbPieces;
private final String peer_source;
private byte[] peer_id;
private final String ip;
protected String ip_resolved;
private IPToHostNameResolverRequest ip_resolver_request;
private int port;
private PeerItem peer_item_identity;
private int tcp_listen_port = 0;
private int udp_listen_port = 0;
private int udp_non_data_port = 0;
// if the peer announces an ipv6 address
private InetAddress alternativeAddress;
private byte crypto_level;
protected PEPeerStats peer_stats;
private final ArrayList requested = new ArrayList();
private final AEMonitor requested_mon = new AEMonitor( "PEPeerTransportProtocol:Req" );
private Map data;
private long lastNeededUndonePieceChange;
private boolean really_choked_by_other_peer = true;
private boolean effectively_choked_by_other_peer = true;
private long effectively_unchoked_time = -1;
protected boolean choking_other_peer = true;
private boolean interested_in_other_peer = false;
private boolean other_peer_interested_in_me = false;
private long snubbed =0;
/** lazy allocation; null until needed */
private volatile BitFlags peerHavePieces =null;
private volatile boolean availabilityAdded =false;
private volatile boolean received_bitfield;
private int[] piece_priority_offsets;
private boolean handshake_sent;
private boolean seeding = false;
private static final byte RELATIVE_SEEDING_NONE = 0x00;
// indicates that the peer told us that he's uploading only
private static final byte RELATIVE_SEEDING_UPLOAD_ONLY_INDICATED = 0x01;
// peer is not useful to us
private static final byte RELATIVE_SEEDING_UPLOAD_ONLY_SEED = 0x02;
private byte relativeSeeding = RELATIVE_SEEDING_NONE;
private final boolean incoming;
protected volatile boolean closing = false;
private volatile int current_peer_state;
final private NetworkConnection connection;
private OutgoingBTPieceMessageHandler outgoing_piece_message_handler;
private OutgoingBTHaveMessageAggregator outgoing_have_message_aggregator;
private Connection plugin_connection;
private boolean identityAdded = false; //needed so we don't remove id's in closeAll() on duplicate connection attempts
protected int connection_state = PEPeerTransport.CONNECTION_PENDING;
private String client = ""; // Client name to show to user.
private String client_peer_id = ""; // Client name derived from the peer ID.
private String client_handshake = ""; // Client name derived from the handshake.
private String client_handshake_version = ""; // Client version derived from the handshake.
//When superSeeding, number of unique piece announced
private int uniquePiece = -1;
//When downloading a piece in exclusivity mode the piece number being downloaded
private int[] reserved_pieces = null;
//Spread time (0 secs , fake default)
private int spreadTimeHint = 0 * 1000;
protected long last_message_sent_time = 0;
protected long last_message_received_time = 0;
protected long last_data_message_received_time = -1;
protected long last_good_data_time =-1; // time data written to disk was recieved
protected long last_data_message_sent_time = -1;
private long connection_established_time = 0;
private int consecutive_no_request_count;
private int messaging_mode = MESSAGING_BT_ONLY;
private Message[] supported_messages = null;
private byte other_peer_bitfield_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_cancel_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_choke_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_handshake_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_bt_have_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_az_have_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_interested_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_keep_alive_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_pex_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_piece_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_unchoke_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_uninterested_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_request_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_suggest_piece_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_have_all_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_have_none_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_reject_request_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_allowed_fast_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_bt_lt_ext_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_az_request_hint_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_az_bad_piece_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_az_stats_request_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private byte other_peer_az_stats_reply_version = BTMessageFactory.MESSAGE_VERSION_INITIAL;
private static final boolean DEBUG_FAST = false;
private boolean ut_pex_enabled = false;
private boolean fast_extension_enabled = false;
private boolean ml_dht_enabled = false;
private static final int ALLOWED_FAST_PIECE_OFFERED_NUM = 10;
private static final int ALLOWED_FAST_OTHER_PEER_PIECE_MAX = 10;
private static final Object KEY_ALLOWED_FAST_RECEIVED = new Object();
private static final Object KEY_ALLOWED_FAST_SENT = new Object();
private final AEMonitor closing_mon = new AEMonitor( "PEPeerTransportProtocol:closing" );
private final AEMonitor general_mon = new AEMonitor( "PEPeerTransportProtocol:data" );
private byte[] handshake_reserved_bytes = null;
private LinkedHashMap recent_outgoing_requests;
private AEMonitor recent_outgoing_requests_mon;
private boolean has_received_initial_pex = false;
private static final boolean SHOW_DISCARD_RATE_STATS;
static {
final String prop = System.getProperty( "show.discard.rate.stats" );
SHOW_DISCARD_RATE_STATS = prop != null && prop.equals( "1" );
}
private static int requests_discarded = 0;
private static int requests_discarded_endgame = 0;
private static int requests_recovered = 0;
private static int requests_completed = 0;
private static final int REQUEST_HINT_MAX_LIFE = PiecePicker.REQUEST_HINT_MAX_LIFE + 30*1000;
private int[] request_hint;
private List peer_listeners_cow;
private final AEMonitor peer_listeners_mon = new AEMonitor( "PEPeerTransportProtocol:PL" );
//certain Optimum Online networks block peer seeding via "complete" bitfield message filtering
//lazy mode makes sure we never send a complete (seed) bitfield
protected static boolean ENABLE_LAZY_BITFIELD;
private boolean priority_connection;
private static final class DisconnectedTransportQueue extends LinkedHashMap
{
public DisconnectedTransportQueue()
{
super(20,0.75F);
}
private static final long MAX_CACHE_AGE = 2*60*1000;
// remove all elements older than 2 minutes until we hit the 20 again
private void performCleaning() {
if(size() > 20)
{
Iterator it = values().iterator();
long now = SystemTime.getMonotonousTime();
while(it.hasNext() && size() > 20)
{
QueueEntry eldest = (QueueEntry)it.next();
if( now - eldest.addTime > MAX_CACHE_AGE){
it.remove();
}else{
break;
}
}
}
}
private static final class QueueEntry {
public QueueEntry(PEPeerTransportProtocol trans)
{
transport = trans;
}
final PEPeerTransportProtocol transport;
final long addTime = SystemTime.getMonotonousTime();
}
// hardcap at 100
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 100;
}
synchronized public Object put(HashWrapper key, PEPeerTransportProtocol value) {
performCleaning();
return super.put(key, new QueueEntry(value));
}
synchronized public PEPeerTransportProtocol remove(HashWrapper key) {
performCleaning();
QueueEntry entry = (QueueEntry)super.remove(key);
if(entry != null)
return entry.transport;
else
return null;
}
}
private static final DisconnectedTransportQueue recentlyDisconnected = new DisconnectedTransportQueue();
private static boolean fast_unchoke_new_peers;
private static final Random rnd = RandomUtils.SECURE_RANDOM;
private static final byte[] sessionSecret;
private static boolean enable_upload_bias;
static {
rnd.setSeed(SystemTime.getHighPrecisionCounter());
sessionSecret = new byte[20];
rnd.nextBytes(sessionSecret);
COConfigurationManager.addAndFireParameterListeners(
new String[]{
"Use Lazy Bitfield",
"Peer.Fast.Initial.Unchoke.Enabled",
"Bias Upload Enable" },
new ParameterListener()
{
public final void
parameterChanged(
String ignore )
{
final String prop = System.getProperty( "azureus.lazy.bitfield" );
ENABLE_LAZY_BITFIELD = prop != null && prop.equals( "1" );
ENABLE_LAZY_BITFIELD |= COConfigurationManager.getBooleanParameter( "Use Lazy Bitfield" );
fast_unchoke_new_peers = COConfigurationManager.getBooleanParameter( "Peer.Fast.Initial.Unchoke.Enabled" );
enable_upload_bias = COConfigurationManager.getBooleanParameter( "Bias Upload Enable" );
}
});
}
// reconnect stuff
private HashWrapper peerSessionID;
private HashWrapper mySessionID;
// allow reconnect if we've sent or recieved at least 1 piece over the current connection
private boolean allowReconnect;
private boolean is_optimistic_unchoke = false;
private PeerExchangerItem peer_exchange_item = null;
private boolean peer_exchange_supported = false;
protected PeerMessageLimiter message_limiter;
private boolean request_hint_supported;
private boolean bad_piece_supported;
private boolean stats_request_supported;
private boolean stats_reply_supported;
private boolean have_aggregation_disabled;
private volatile boolean manual_lazy_bitfield_control;
private volatile int[] manual_lazy_haves;
//INCOMING
public
PEPeerTransportProtocol(
PEPeerControl _manager,
String _peer_source,
NetworkConnection _connection,
Map _initial_user_data )
{
manager = _manager;
peer_source = _peer_source;
connection = _connection;
data = _initial_user_data;
incoming = true;
diskManager =manager.getDiskManager();
piecePicker =manager.getPiecePicker();
nbPieces =diskManager.getNbPieces();
InetSocketAddress notional_address = _connection.getEndpoint().getNotionalAddress();
ip = notional_address.getAddress().getHostAddress();
port = notional_address.getPort();
peer_item_identity = PeerItemFactory.createPeerItem( ip, port, PeerItem.convertSourceID( _peer_source ), PeerItemFactory.HANDSHAKE_TYPE_PLAIN, 0, PeerItemFactory.CRYPTO_LEVEL_1, 0 ); //this will be recreated upon az handshake decode
plugin_connection = new ConnectionImpl(connection, incoming);
peer_stats = manager.createPeerStats( this );
changePeerState( PEPeer.CONNECTING );
}
public void
start()
{
// split out connection initiation from constructor so we can get access to the peer transport
// before message processing starts
if ( incoming ){
//"fake" a connect request to register our listener
connection.connect(
ProtocolEndpoint.CONNECT_PRIORITY_MEDIUM,
new NetworkConnection.ConnectionListener()
{
public final int
connectStarted( int ct ){
connection_state = PEPeerTransport.CONNECTION_CONNECTING;
return( ct );
}
public final void connectSuccess( ByteBuffer remaining_initial_data ) { //will be called immediately
if (Logger.isEnabled())
Logger.log(new LogEvent(PEPeerTransportProtocol.this, LOGID,
"In: Established incoming connection"));
generateSessionId();
initializeConnection();
/*
* Waiting until we've received the initiating-end's full handshake, before sending back our own,
* really should be the "proper" behavior. However, classic BT trackers running NAT checking will
* only send the first 48 bytes (up to infohash) of the peer handshake, skipping peerid, which means
* we'll never get their complete handshake, and thus never reply, which causes the NAT check to fail.
* So, we need to send our handshake earlier, after we've verified the infohash.
* NOTE:
* This code makes the assumption that the inbound infohash has already been validated,
* as we don't check their handshake fully before sending our own.
*/
sendBTHandshake();
}
public final void
connectFailure(
Throwable failure_msg )
{ //should never happen
Debug.out( "ERROR: incoming connect failure: ", failure_msg );
closeConnectionInternally( "ERROR: incoming connect failure [" + PEPeerTransportProtocol.this + "] : " + failure_msg.getMessage(), true, true );
}
public final void
exceptionThrown(
Throwable error )
{
if( error.getMessage() == null ) {
Debug.out( error );
}
closeConnectionInternally( "connection exception: " + error.getMessage(), false, true );
}
public String
getDescription()
{
return( getString());
}
});
}else{
// not pulled out startup from outbound connections yet...
}
}
//OUTGOING
public
PEPeerTransportProtocol(
PEPeerControl _manager,
String _peer_source,
String _ip,
int _tcp_port,
int _udp_port,
boolean _use_tcp,
boolean _require_crypto_handshake,
byte _crypto_level,
Map _initial_user_data )
{
manager = _manager;
diskManager =manager.getDiskManager();
piecePicker =manager.getPiecePicker();
nbPieces =diskManager.getNbPieces();
lastNeededUndonePieceChange =Long.MIN_VALUE;
peer_source = _peer_source;
ip = _ip;
port = _tcp_port;
tcp_listen_port = _tcp_port;
udp_listen_port = _udp_port;
crypto_level = _crypto_level;
data = _initial_user_data;
if ( data != null ){
Boolean pc = (Boolean)data.get( Peer.PR_PRIORITY_CONNECTION );
if ( pc != null && pc.booleanValue()){
setPriorityConnection( true );
}
}
udp_non_data_port = UDPNetworkManager.getSingleton().getUDPNonDataListeningPortNumber();
peer_item_identity = PeerItemFactory.createPeerItem( ip, tcp_listen_port, PeerItem.convertSourceID( _peer_source ), PeerItemFactory.HANDSHAKE_TYPE_PLAIN, _udp_port, crypto_level, 0 ); //this will be recreated upon az handshake decode
incoming = false;
peer_stats = manager.createPeerStats( this );
if( port < 0 || port > 65535 ) {
closeConnectionInternally( "given remote port is invalid: " + port );
connection = null;
return;
}
// either peer specific or global pref plus optional per-download level
boolean use_crypto =
_require_crypto_handshake ||
NetworkManager.getCryptoRequired( manager.getAdapter().getCryptoLevel());
if( isLANLocal() ) use_crypto = false; //dont bother with PHE for lan peers
InetSocketAddress endpoint_address;
ProtocolEndpoint pe;
if ( _use_tcp ){
endpoint_address = new InetSocketAddress( ip, tcp_listen_port );
pe = ProtocolEndpointFactory.createEndpoint( ProtocolEndpoint.PROTOCOL_TCP, endpoint_address );
}else{
endpoint_address = new InetSocketAddress( ip, udp_listen_port );
pe = ProtocolEndpointFactory.createEndpoint( ProtocolEndpoint.PROTOCOL_UDP, endpoint_address );
}
ConnectionEndpoint connection_endpoint = new ConnectionEndpoint( endpoint_address );
connection_endpoint.addProtocol( pe );
connection =
NetworkManager.getSingleton().createConnection(
connection_endpoint,
new BTMessageEncoder(),
new BTMessageDecoder(),
use_crypto,
!_require_crypto_handshake,
manager.getSecrets( _crypto_level ));
plugin_connection = new ConnectionImpl(connection, incoming);
changePeerState( PEPeer.CONNECTING );
ByteBuffer initial_outbound_data = null;
if ( use_crypto ){
BTHandshake handshake = new BTHandshake( manager.getHash(),
manager.getPeerId(),
manager.isExtendedMessagingEnabled(), other_peer_handshake_version );
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID,
"Sending encrypted handshake with reserved bytes: " +
ByteFormatter.nicePrint(handshake.getReserved(), false)));
DirectByteBuffer[] ddbs = handshake.getRawData();
int handshake_len = 0;
for (int i=0;i<ddbs.length;i++){
handshake_len += ddbs[i].remaining( DirectByteBuffer.SS_PEER );
}
initial_outbound_data = ByteBuffer.allocate( handshake_len );
for (int i=0;i<ddbs.length;i++){
DirectByteBuffer ddb = ddbs[i];
initial_outbound_data.put( ddb.getBuffer( DirectByteBuffer.SS_PEER ));
ddb.returnToPool();
}
initial_outbound_data.flip();
handshake_sent = true;
}
int priority;
if ( manager.isSeeding()){
priority = ProtocolEndpoint.CONNECT_PRIORITY_LOW;
}else if ( manager.isRTA()){
if ( PeerClassifier.isAzureusIP( ip )){
priority = ProtocolEndpoint.CONNECT_PRIORITY_SUPER_HIGHEST;
}else{
priority = ProtocolEndpoint.CONNECT_PRIORITY_HIGHEST;
}
}else if ( PeerClassifier.isAzureusIP( ip )){
priority = ProtocolEndpoint.CONNECT_PRIORITY_HIGHEST;
}else{
priority = ProtocolEndpoint.CONNECT_PRIORITY_MEDIUM;
}
// give plugin peers some priority as they're being injected for a good reason
if ( peer_source == PEPeerSource.PS_PLUGIN ){
if ( priority > ProtocolEndpoint.CONNECT_PRIORITY_HIGH ){
priority = ProtocolEndpoint.CONNECT_PRIORITY_HIGH;
}
}
connection.connect(
initial_outbound_data,
priority,
new NetworkConnection.ConnectionListener()
{
private boolean connect_ok;
public final int
connectStarted(
int default_connect_timeout )
{
connection_state = PEPeerTransport.CONNECTION_CONNECTING;
if ( default_connect_timeout <= 0 ){
return( default_connect_timeout );
}
return( manager.getConnectTimeout( default_connect_timeout ));
}
public final void
connectSuccess(
ByteBuffer remaining_initial_data )
{
connect_ok = true;
if( closing ) {
//Debug.out( "PEPeerTransportProtocol::connectSuccess() called when closing." );
return;
}
generateSessionId();
if (Logger.isEnabled())
Logger.log(new LogEvent(PEPeerTransportProtocol.this, LOGID,
"Out: Established outgoing connection"));
initializeConnection();
if ( remaining_initial_data != null && remaining_initial_data.remaining() > 0){
// queue as a *raw* message as already encoded
connection.getOutgoingMessageQueue().addMessage(
new BTRawMessage( new DirectByteBuffer( remaining_initial_data )), false );
}
sendBTHandshake();
}
public final void
connectFailure(
Throwable failure_msg )
{
closeConnectionInternally( "failed to establish outgoing connection: " + failure_msg.getMessage(), true, true );
}
public final void
exceptionThrown(
Throwable error )
{
if( error.getMessage() == null ) {
Debug.out( "error.getMessage() == null", error );
}
closeConnectionInternally( "connection exception: " + error.getMessage(), !connect_ok, true );
}
public String
getDescription()
{
return( getString());
}
});
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID,
"Out: Creating outgoing connection"));
}
protected void initializeConnection() {
if( closing ) return;
recent_outgoing_requests = new LinkedHashMap( 16, .75F, true ) {
public final boolean removeEldestEntry(Map.Entry eldest) {
return size() > 16;
}
};
recent_outgoing_requests_mon = new AEMonitor( "PEPeerTransportProtocol:ROR" );
message_limiter = new PeerMessageLimiter();
/*
//link in outgoing piece handler
outgoing_piece_message_handler =
new OutgoingBTPieceMessageHandler(
this,
connection.getOutgoingMessageQueue(),
new OutgoingBTPieceMessageHandlerAdapter()
{
public void
diskRequestCompleted(
long bytes)
{
peer_stats.diskReadComplete( bytes );
}
},
other_peer_piece_version);*/
//link in outgoing have message aggregator
outgoing_have_message_aggregator = new OutgoingBTHaveMessageAggregator( connection.getOutgoingMessageQueue(), other_peer_bt_have_version, other_peer_az_have_version );
connection_established_time = SystemTime.getCurrentTime();
connection_state = PEPeerTransport.CONNECTION_WAITING_FOR_HANDSHAKE;
changePeerState( PEPeer.HANDSHAKING );
registerForMessageHandling();
}
public String
getPeerSource()
{
return( peer_source );
}
/**
* Close the peer connection from within the PEPeerTransport object.
* @param reason
*/
protected void closeConnectionInternally( String reason, boolean connect_failed, boolean network_failure ) {
performClose( reason, connect_failed, false, network_failure );
}
protected void closeConnectionInternally( String reason ) {
performClose( reason, false, false, false );
}
/**
* Close the peer connection from the PEPeerControl manager side.
* NOTE: This method assumes PEPeerControl already knows about the close.
* This method is inteded to be only invoked by select administrative methods.
* You probably should not invoke this directly.
*/
public void closeConnection( String reason ) {
performClose( reason, false, true, false );
}
private void performClose( String reason, boolean connect_failed, boolean externally_closed, boolean network_failure )
{
try{
closing_mon.enter();
if( closing )
return;
closing = true;
// immediatly lose interest in peer
interested_in_other_peer =false;
lastNeededUndonePieceChange =Long.MAX_VALUE;
if (isSnubbed())
manager.decNbPeersSnubbed();
if( identityAdded ) { //remove identity
if( peer_id != null )
PeerIdentityManager.removeIdentity( manager.getPeerIdentityDataID(), peer_id, getPort());
else
Debug.out( "PeerIdentity added but peer_id == null !!!" );
identityAdded = false;
}
changePeerState( PEPeer.CLOSING );
}finally{
closing_mon.exit();
}
//cancel any pending requests (on the manager side)
cancelRequests();
if( outgoing_have_message_aggregator != null ) {
outgoing_have_message_aggregator.destroy();
}
if( peer_exchange_item != null ) {
peer_exchange_item.destroy();
}
if( outgoing_piece_message_handler != null ) {
outgoing_piece_message_handler.destroy();
}
if( connection != null ) { //can be null if close is called within ::<init>::, like when the given port is invalid
connection.close( reason );
}
if ( ip_resolver_request != null ){
ip_resolver_request.cancel();
}
removeAvailability();
changePeerState( PEPeer.DISCONNECTED );
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID, "Peer connection closed: " + reason));
if( !externally_closed ) { //if closed internally, notify manager, otherwise we assume it already knows
manager.peerConnectionClosed( this, connect_failed, network_failure );
}
setPriorityConnection( false );
/*
* all managed references should have been removed by now
* add to recently disconnected list and null some stuff to make the object lighter
*/
outgoing_have_message_aggregator = null;
peer_exchange_item = null;
outgoing_piece_message_handler = null;
plugin_connection = null;
// only save stats if it's worth doing so; ignore rapid connect-disconnects
if (peer_stats.getTotalDataBytesReceived() > 0 || peer_stats.getTotalDataBytesSent() > 0 || SystemTime.getCurrentTime() - connection_established_time > 30 * 1000)
recentlyDisconnected.put(mySessionID, this);
}
public PEPeerTransport reconnect(boolean tryUDP, boolean tryIPv6) {
boolean use_tcp = isTCP() && !(tryUDP && getUDPListenPort() > 0);
if ((use_tcp && getTCPListenPort() > 0) || (!use_tcp && getUDPListenPort() > 0))
{
boolean use_crypto = getPeerItemIdentity().getHandshakeType() == PeerItemFactory.HANDSHAKE_TYPE_CRYPTO;
PEPeerTransport new_conn =
PEPeerTransportFactory.createTransport(
manager,
getPeerSource(),
tryIPv6 && alternativeAddress != null ? alternativeAddress.getHostAddress() : getIp(),
getTCPListenPort(),
getUDPListenPort(),
use_tcp,
use_crypto,
crypto_level,
null );
// log to both relations
Logger.log(new LogEvent(new Object[] {this, new_conn},LOGID,"attempting to reconnect, creating new connection"));
if (new_conn instanceof PEPeerTransportProtocol)
{
PEPeerTransportProtocol pt = (PEPeerTransportProtocol) new_conn;
pt.checkForReconnect(mySessionID);
// carry over the alt address in case the reconnect fails and we try again with ipv6
pt.alternativeAddress = alternativeAddress;
}
manager.addPeer( new_conn );
return (new_conn);
} else
{
return (null);
}
}
/* (non-Javadoc)
* @see org.gudy.azureus2.core3.peer.impl.PEPeerTransport#isSafeForReconnect()
*/
public boolean isSafeForReconnect() {
return allowReconnect;
}
private void checkForReconnect(HashWrapper oldID)
{
PEPeerTransportProtocol oldTransport = recentlyDisconnected.remove(oldID);
if(oldTransport != null)
{
Logger.log(new LogEvent(this,LOGID,LogAlert.AT_INFORMATION,"reassociating stats from "+oldTransport+" with this connection"));
peerSessionID = oldTransport.peerSessionID;
peer_stats = oldTransport.peer_stats;
peer_stats.setPeer(this);
setSnubbed(oldTransport.isSnubbed());
snubbed = oldTransport.snubbed;
last_good_data_time = oldTransport.last_good_data_time;
}
}
private void generateSessionId()
{
SHA1Hasher sha1 = new SHA1Hasher();
sha1.update(sessionSecret);
sha1.update(manager.getHash());
sha1.update(getIp().getBytes());
mySessionID = sha1.getHash();
checkForReconnect(mySessionID);
}
private void addAvailability()
{
if (!availabilityAdded &&!closing &&peerHavePieces !=null &¤t_peer_state ==PEPeer.TRANSFERING)
{
final List peer_listeners_ref =peer_listeners_cow;
if (peer_listeners_ref !=null)
{
for (int i =0; i <peer_listeners_ref.size(); i++)
{
final PEPeerListener peerListener =(PEPeerListener) peer_listeners_ref.get(i);
peerListener.addAvailability(this, peerHavePieces);
}
availabilityAdded =true;
}
}
}
private void removeAvailability()
{
if (availabilityAdded &&peerHavePieces !=null)
{
final List peer_listeners_ref =peer_listeners_cow;
if (peer_listeners_ref !=null)
{
for (int i =0; i <peer_listeners_ref.size(); i++)
{
final PEPeerListener peerListener =(PEPeerListener) peer_listeners_ref.get(i);
peerListener.removeAvailability(this, peerHavePieces);
}
}
availabilityAdded =false;
}
peerHavePieces =null;
}
protected void sendBTHandshake() {
if ( !handshake_sent ){
BTHandshake handshake = new BTHandshake( manager.getHash(),
manager.getPeerId(),
manager.isExtendedMessagingEnabled(), other_peer_handshake_version );
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID,
"Sending handshake with reserved bytes: " +
ByteFormatter.nicePrint(handshake.getReserved(), false)));
/**
* AMC's hopefully temporary debug code - some Az clients out there appear to be be
* AZMP neutered and communicating using LTEP instead. Let's bug the user if
* this happens so that I know about it.
*/
if (Constants.isCVSVersion()) {
byte[] reserved = handshake.getReserved();
boolean supports_azmp = (reserved[0] & 128) == 128;
boolean supports_ltep = (reserved[5] & 16) == 16;
if (supports_ltep && !supports_azmp) {
Logger.log(new LogAlert(this, LogAlert.UNREPEATABLE, LogAlert.AT_ERROR,
"AZMP support has failed in Azureus, please report this in the " +
"<a href=\"http://forum.vuze.com/forum.jspa?forumID=4\">CVS bug report forum</a>.\n" +
"Debug data: " + ByteFormatter.nicePrint(reserved)
));
}
}
connection.getOutgoingMessageQueue().addMessage(handshake, false);
}
}
// We could do this in a more automated way in future, but hardcoded is simple and quick,
// so we'll do that instead. :)
static Map lt_ext_map = UTPeerExchange.ENABLED ? Collections.singletonMap("ut_pex", new Integer(1) ) : Collections.EMPTY_MAP;
private void sendLTHandshake() {
String client_name = Constants.AZUREUS_NAME + " " + Constants.AZUREUS_VERSION;
int localTcpPort = TCPNetworkManager.getSingleton().getTCPListeningPortNumber();
String tcpPortOverride = COConfigurationManager.getStringParameter("TCP.Listen.Port.Override");
try
{
localTcpPort = Integer.parseInt(tcpPortOverride);
} catch (NumberFormatException e) {} // ignore as invalid input
boolean require_crypto = NetworkManager.getCryptoRequired( manager.getAdapter().getCryptoLevel());
Map data_dict = new HashMap();
data_dict.put("m", lt_ext_map);
data_dict.put("v", client_name);
data_dict.put("p", new Integer(localTcpPort));
data_dict.put("e", new Long(require_crypto ? 1L : 0L));
data_dict.put("upload_only", new Long(manager.isSeeding() && !( ENABLE_LAZY_BITFIELD || manual_lazy_bitfield_control )? 1L : 0L));
InetAddress defaultV6 = NetworkAdmin.getSingleton().hasIPV6Potential(true) ? NetworkAdmin.getSingleton().getDefaultPublicAddressV6() : null;
if(defaultV6 != null)
data_dict.put("ipv6",defaultV6.getAddress());
LTHandshake lt_handshake = new LTHandshake(
data_dict, other_peer_bt_lt_ext_version
);
connection.getOutgoingMessageQueue().addMessage(lt_handshake, false);
}
private void sendAZHandshake() {
final Message[] avail_msgs = MessageManager.getSingleton().getRegisteredMessages();
final String[] avail_ids = new String[avail_msgs.length];
final byte[] avail_vers = new byte[avail_msgs.length];
for (int i = 0; i < avail_msgs.length; i++)
{
avail_ids[i] = avail_msgs[i].getID();
avail_vers[i] = avail_msgs[i].getVersion();
}
int local_tcp_port = TCPNetworkManager.getSingleton().getTCPListeningPortNumber();
int local_udp_port = UDPNetworkManager.getSingleton().getUDPListeningPortNumber();
int local_udp2_port = UDPNetworkManager.getSingleton().getUDPNonDataListeningPortNumber();
String tcpPortOverride = COConfigurationManager.getStringParameter("TCP.Listen.Port.Override");
try
{
local_tcp_port = Integer.parseInt(tcpPortOverride);
} catch (NumberFormatException e)
{} // ignore as invalid input
boolean require_crypto = NetworkManager.getCryptoRequired(manager.getAdapter().getCryptoLevel());
/*
* we always send the Az-handshake immediately after the BT-handshake, before decoding the
* other side's Az-handshake, thus there should be no peerSessionID unless this is a
* reconnect
*/
if(peerSessionID != null)
Logger.log(new LogEvent(this, LOGID, LogEvent.LT_INFORMATION,"notifying peer of reconnect attempt"));
AZHandshake az_handshake = new AZHandshake(
AZPeerIdentityManager.getAZPeerIdentity(),
mySessionID,
peerSessionID,
Constants.AZUREUS_NAME,
Constants.AZUREUS_VERSION,
local_tcp_port,
local_udp_port,
local_udp2_port,
NetworkAdmin.getSingleton().hasIPV6Potential(true) ? NetworkAdmin.getSingleton().getDefaultPublicAddressV6() : null,
avail_ids,
avail_vers,
require_crypto ? AZHandshake.HANDSHAKE_TYPE_CRYPTO : AZHandshake.HANDSHAKE_TYPE_PLAIN,
other_peer_handshake_version,
manager.isSeeding() && ! ( ENABLE_LAZY_BITFIELD || manual_lazy_bitfield_control ));
connection.getOutgoingMessageQueue().addMessage( az_handshake, false );
}
public int getPeerState() { return current_peer_state; }
public boolean isDownloadPossible()
{
if (!closing &&!effectively_choked_by_other_peer)
{
if (lastNeededUndonePieceChange <piecePicker.getNeededUndonePieceChange())
{
checkInterested();
lastNeededUndonePieceChange =piecePicker.getNeededUndonePieceChange();
}
if (interested_in_other_peer &¤t_peer_state ==PEPeer.TRANSFERING)
return true;
}
return false;
}
public int getPercentDoneInThousandNotation()
{
long total_done = getBytesDownloaded();
return (int)((total_done *1000) /diskManager.getTotalLength());
}
public boolean transferAvailable() {
return (!effectively_choked_by_other_peer && interested_in_other_peer);
}
private void printRequestStats() {
if( SHOW_DISCARD_RATE_STATS ) {
final float discard_perc = (requests_discarded * 100F) / ((requests_completed + requests_recovered + requests_discarded) * 1F);
final float discard_perc_end = (requests_discarded_endgame * 100F) / ((requests_completed + requests_recovered + requests_discarded_endgame) * 1F);
final float recover_perc = (requests_recovered * 100F) / ((requests_recovered + requests_discarded) * 1F);
System.out.println( "c="+requests_completed+ " d="+requests_discarded+ " de="+requests_discarded_endgame+ " r="+requests_recovered+ " dp="+discard_perc+ "% dpe="+discard_perc_end+ "% rp="+recover_perc+ "%" );
}
}
/**
* Checks if this peer is a seed or not by trivially checking if
* thier Have bitflags exisits and shows a number of bits set equal
* to the torrent # of pieces (and the torrent # of pieces is >0)
*/
private void checkSeed()
{
// seed implicitly means *something* to send (right?)
if (peerHavePieces !=null && nbPieces >0)
setSeed((peerHavePieces.nbSet ==nbPieces));
else
setSeed(false);
if(manager.isSeeding() && isSeed())
// peer is a real seed so it's also a relative seed
relativeSeeding |= RELATIVE_SEEDING_UPLOAD_ONLY_SEED;
else if(manager.isSeeding() && (relativeSeeding & RELATIVE_SEEDING_UPLOAD_ONLY_INDICATED) != 0)
// peer indicated upload-only, we're seeding so he's a relative seed
relativeSeeding |= RELATIVE_SEEDING_UPLOAD_ONLY_SEED;
else if(peerHavePieces !=null && nbPieces > 0)
{
int piecesDone = manager.getPiecePicker().getNbPiecesDone();
DiskManagerPiece[] dmPieces = diskManager.getPieces();
boolean couldBeSeed = true;
if(!manager.isSeeding() && (relativeSeeding & RELATIVE_SEEDING_UPLOAD_ONLY_INDICATED) != 0)
{ /*
* peer indicated upload-only, check if we can use any of the data, otherwise flag
* as relative seed. Useful to disconnect not-useful pseudo-seeds during downloading
*/
for(int i = peerHavePieces.start;i <= peerHavePieces.end;i++)
{
// relative seed if peer doesn't have the piece, we already have it or we don't need it
couldBeSeed &= !peerHavePieces.flags[i] || dmPieces[i].isDone() || !dmPieces[i].isNeeded();
if(!couldBeSeed)
break;
}
} else if(manager.isSeeding() && piecesDone <= peerHavePieces.nbSet)
{ // we're seeding, check if peer has all the data we have (and more), flag as relative seed if so
for(int i = peerHavePieces.start;i <= peerHavePieces.end;i++)
{
// relative seed if we don't have the piece or we have it and the peer has it too
couldBeSeed &= !(dmPieces[i].isDone()) || peerHavePieces.flags[i];
if(!couldBeSeed)
break;
}
} else
couldBeSeed = false;
if(couldBeSeed)
relativeSeeding |= RELATIVE_SEEDING_UPLOAD_ONLY_SEED;
else
relativeSeeding &= ~RELATIVE_SEEDING_UPLOAD_ONLY_SEED;
} else
relativeSeeding &= ~RELATIVE_SEEDING_UPLOAD_ONLY_SEED;
}
public DiskManagerReadRequest request(final int pieceNumber, final int pieceOffset, final int pieceLength) {
final DiskManagerReadRequest request =manager.createDiskManagerRequest(pieceNumber, pieceOffset, pieceLength);
if (current_peer_state != TRANSFERING) {
manager.requestCanceled(request);
return null;
}
boolean added =false;
try{
requested_mon.enter();
if (!requested.contains(request)){
requested.add(request);
added = true;
}
}finally{
requested_mon.exit();
}
if ( added ){
connection.getOutgoingMessageQueue().addMessage( new BTRequest( pieceNumber, pieceOffset, pieceLength, other_peer_request_version ), false );
_lastPiece =pieceNumber;
if ( DEBUG_FAST ){
if ( really_choked_by_other_peer ){
System.out.println( "Sending allow-fast request for " + pieceNumber + "/" + pieceOffset + "/" + pieceLength + " to " + getIp());
}
}
try{
recent_outgoing_requests_mon.enter();
recent_outgoing_requests.put( request, null );
}finally{
recent_outgoing_requests_mon.exit();
}
}
return request;
}
public int
getRequestIndex(
DiskManagerReadRequest request )
{
try{
requested_mon.enter();
return( requested.indexOf( request ));
}finally{
requested_mon.exit();
}
}
public void sendCancel( DiskManagerReadRequest request ) {
if ( current_peer_state != TRANSFERING ) return;
if ( hasBeenRequested( request ) ) {
removeRequest( request );
connection.getOutgoingMessageQueue().addMessage( new BTCancel( request.getPieceNumber(), request.getOffset(), request.getLength(), other_peer_cancel_version ), false );
}
}
public void sendHave( int pieceNumber ) {
if ( current_peer_state != TRANSFERING || pieceNumber == manager.getHiddenPiece()) return;
//only force if the other peer doesn't have this piece and is not yet interested or we;ve disabled
// aggregation
final boolean force =!other_peer_interested_in_me &&peerHavePieces !=null &&!peerHavePieces.flags[pieceNumber];
outgoing_have_message_aggregator.queueHaveMessage( pieceNumber, force || have_aggregation_disabled );
checkInterested();
}
public void sendChoke() {
if ( current_peer_state != TRANSFERING ) return;
//System.out.println( "["+(System.currentTimeMillis()/1000)+"] " +connection + " choked");
connection.getOutgoingMessageQueue().addMessage( new BTChoke( other_peer_choke_version ), false );
choking_other_peer = true;
is_optimistic_unchoke = false;
destroyPieceMessageHandler();
}
public void sendUnChoke() {
if ( current_peer_state != TRANSFERING ) return;
//System.out.println( "["+(System.currentTimeMillis()/1000)+"] " +connection + " unchoked");
createPieceMessageHandler();
choking_other_peer = false; // set this first as with pseudo peers we can effectively synchronously act
// on the unchoke advice and we don't want that borking with choked still set
connection.getOutgoingMessageQueue().addMessage( new BTUnchoke(other_peer_unchoke_version), false );
}
private void
createPieceMessageHandler()
{
if ( outgoing_piece_message_handler == null ){
outgoing_piece_message_handler = new OutgoingBTPieceMessageHandler(
this,
connection.getOutgoingMessageQueue(),
new OutgoingBTPieceMessageHandlerAdapter()
{
public void
diskRequestCompleted(
long bytes)
{
peer_stats.diskReadComplete( bytes );
}
},
other_peer_piece_version);
}
}
private void
destroyPieceMessageHandler()
{
if (outgoing_piece_message_handler != null ){
outgoing_piece_message_handler.removeAllPieceRequests();
outgoing_piece_message_handler.destroy();
outgoing_piece_message_handler = null;
}
}
private void sendKeepAlive() {
if ( current_peer_state != TRANSFERING ) return;
if( outgoing_have_message_aggregator.hasPending() ) {
outgoing_have_message_aggregator.forceSendOfPending();
}
else {
connection.getOutgoingMessageQueue().addMessage( new BTKeepAlive(other_peer_keep_alive_version), false );
}
}
private void sendMainlineDHTPort() {
if (!this.ml_dht_enabled) {return;}
MainlineDHTProvider provider = getDHTProvider();
if (provider == null) {return;}
Message message = new BTDHTPort(provider.getDHTPort());
connection.getOutgoingMessageQueue().addMessage(message, false);
}
/**
* Global checkInterested method.
* Early-out scan of pieces to determine if the peer is interesting or not.
* They're interesting if they have a piece that we Need and isn't Done
*/
public void checkInterested()
{
if (closing ||peerHavePieces ==null ||peerHavePieces.nbSet ==0)
return;
boolean is_interesting = false;
if (piecePicker.hasDownloadablePiece())
{ // there is a piece worth being interested in
if (!isSeed() && !isRelativeSeed())
{ // check individually if don't have all
for (int i =peerHavePieces.start; i <=peerHavePieces.end; i++ )
{
if (peerHavePieces.flags[i] && diskManager.isInteresting(i))
{
is_interesting = true;
break;
}
}
} else
is_interesting =true;
}
if (is_interesting &&!interested_in_other_peer)
connection.getOutgoingMessageQueue().addMessage(new BTInterested(other_peer_interested_version), false);
else if (!is_interesting &&interested_in_other_peer)
connection.getOutgoingMessageQueue().addMessage(new BTUninterested(other_peer_uninterested_version), false);
interested_in_other_peer = is_interesting;
}
/** @deprecated no longer used by CVS code
* Checks if a particular piece makes us interested in the peer
* @param pieceNumber the piece number that has been received
*/
/*
private void checkInterested(int pieceNumber)
{
if (closing)
return;
// Do we need this piece and it's not Done?
if (!interested_in_other_peer &&diskManager.isInteresting(pieceNumber))
{
connection.getOutgoingMessageQueue().addMessage( new BTInterested(), false );
interested_in_other_peer =true;
}
}
*/
/**
* Private method to send the bitfield.
*/
private void sendBitField()
{
if (closing)
return;
//In case we're in super seed mode, we don't send our bitfield
if (manager.isSuperSeedMode())
return;
//create bitfield
final DirectByteBuffer buffer =DirectByteBufferPool.getBuffer(DirectByteBuffer.AL_MSG, (nbPieces +7) /8);
final DiskManagerPiece[] pieces =diskManager.getPieces();
int num_pieces = pieces.length;
HashSet lazies = null;
int[] lazy_haves = null;
if (ENABLE_LAZY_BITFIELD || manual_lazy_bitfield_control )
{
int bits_in_first_byte = Math.min(num_pieces, 8);
int last_byte_start_bit = (num_pieces / 8) * 8;
int bits_in_last_byte = num_pieces - last_byte_start_bit;
if (bits_in_last_byte == 0)
{
bits_in_last_byte = 8;
last_byte_start_bit -= 8;
}
lazies = new HashSet();
// one bit from first byte
int first_byte_entry = rnd.nextInt(bits_in_first_byte);
if (pieces[first_byte_entry].isDone())
{
lazies.add(new MutableInteger(first_byte_entry));
}
// one bit from last byte
int last_byte_entry = last_byte_start_bit + rnd.nextInt(bits_in_last_byte);
if (pieces[last_byte_entry].isDone())
{
lazies.add(new MutableInteger(last_byte_entry));
}
// random others missing
int other_lazies = rnd.nextInt(16) + 4;
for (int i = 0; i < other_lazies; i++)
{
int random_entry = rnd.nextInt(num_pieces);
if (pieces[random_entry].isDone())
{
lazies.add(new MutableInteger(random_entry));
}
}
int num_lazy = lazies.size();
if (num_lazy == 0)
{
lazies = null;
} else
{
lazy_haves = new int[num_lazy];
Iterator it = lazies.iterator();
for (int i = 0; i < num_lazy; i++)
{
int lazy_have = ((MutableInteger) it.next()).getValue();
lazy_haves[i] = lazy_have;
}
if (num_lazy > 1)
{
for (int i = 0; i < num_lazy; i++)
{
int swap = rnd.nextInt(num_lazy);
if (swap != i)
{
int temp = lazy_haves[swap];
lazy_haves[swap] = lazy_haves[i];
lazy_haves[i] = temp;
}
}
}
}
}
int bToSend = 0;
int i = 0;
MutableInteger mi = new MutableInteger(0);
int hidden_piece = manager.getHiddenPiece();
for (; i <num_pieces; i++ ){
if ((i %8) ==0){
bToSend =0;
}
bToSend = bToSend << 1;
if (pieces[i].isDone() && i != hidden_piece ){
if ( lazies != null ){
mi.setValue(i);
if ( lazies.contains( mi )){
// System.out.println( "LazySet: " + getIp() + " -> " + i );
}else{
bToSend += 1;
}
}else{
bToSend += 1;
}
}
if ((i %8) ==7){
buffer.put(DirectByteBuffer.SS_BT, (byte) bToSend);
}
}
if ((i %8) !=0){
bToSend = bToSend << (8 - (i % 8));
buffer.put( DirectByteBuffer.SS_BT, (byte)bToSend );
}
buffer.flip( DirectByteBuffer.SS_BT );
connection.getOutgoingMessageQueue().addMessage(new BTBitfield(buffer, other_peer_bitfield_version ), false);
if ( lazy_haves != null ){
if ( manual_lazy_bitfield_control ){
manual_lazy_haves = lazy_haves;
}else{
sendLazyHaves( lazy_haves, false );
}
}
}
protected void
sendLazyHaves(
final int[] lazy_haves,
boolean immediate )
{
if ( immediate ){
if ( current_peer_state == TRANSFERING ){
for ( int lazy_have: lazy_haves ){
connection.getOutgoingMessageQueue().addMessage(new BTHave(lazy_have, other_peer_bt_have_version), false);
}
}
}else{
SimpleTimer.addEvent(
"LazyHaveSender",
SystemTime.getCurrentTime() + 1000 + rnd.nextInt(2000),
new TimerEventPerformer()
{
int next_have = 0;
public void perform(TimerEvent event) {
if ( current_peer_state == TRANSFERING ){
int lazy_have = lazy_haves[next_have++];
connection.getOutgoingMessageQueue().addMessage(new BTHave(lazy_have, other_peer_bt_have_version), false);
if ( next_have < lazy_haves.length && current_peer_state == TRANSFERING ){
SimpleTimer.addEvent("LazyHaveSender", SystemTime.getCurrentTime() + rnd.nextInt(2000), this);
}
}
}
});
}
}
public byte[] getId() { return peer_id; }
public String getIp() { return ip; }
public InetAddress getAlternativeIPv6() { return alternativeAddress; }
public int getPort() { return port; }
public int getTCPListenPort() { return tcp_listen_port; }
public int getUDPListenPort() { return udp_listen_port; }
public int getUDPNonDataListenPort() { return( udp_non_data_port ); }
public String getClient() { return client; }
public boolean isIncoming() { return incoming; }
public boolean isOptimisticUnchoke() { return is_optimistic_unchoke && !isChokedByMe(); }
public void setOptimisticUnchoke( boolean is_optimistic ) { is_optimistic_unchoke = is_optimistic; }
public PEPeerControl getControl() { return manager; }
public PEPeerManager getManager() { return manager; }
public PEPeerStats getStats() { return peer_stats; }
public int[]
getPriorityOffsets()
{
return( piece_priority_offsets );
}
public boolean
requestAllocationStarts(
int[] base_priorities )
{
return( false );
}
public void
requestAllocationComplete()
{
}
/**
* @return null if no bitfield has been recieved yet
* else returns BitFlags indicating what pieces the peer has
*/
public BitFlags getAvailable()
{
return peerHavePieces;
}
public boolean isPieceAvailable(int pieceNumber)
{
if (peerHavePieces !=null)
return peerHavePieces.flags[pieceNumber];
return false;
}
public boolean isChokingMe() { return effectively_choked_by_other_peer; }
public boolean isUnchokeOverride() {return really_choked_by_other_peer && !effectively_choked_by_other_peer; }
public boolean isChokedByMe() { return choking_other_peer; }
/**
* @return true if the peer is interesting to us
*/
public boolean isInteresting() { return interested_in_other_peer; }
/**
* @return true if the peer is interested in what we're offering
*/
public boolean isInterested() { return other_peer_interested_in_me; }
public boolean isSeed() { return seeding; }
public boolean isRelativeSeed() { return (relativeSeeding & RELATIVE_SEEDING_UPLOAD_ONLY_SEED) != 0; }
private void
setSeed(
boolean s )
{
if ( seeding != s ){
seeding = s;
PeerExchangerItem pex_item = peer_exchange_item;
if ( pex_item != null && s){
pex_item.seedStatusChanged();
}
}
}
public boolean isSnubbed() { return snubbed !=0; }
public long getSnubbedTime()
{
if (snubbed ==0)
return 0;
final long now =SystemTime.getCurrentTime();
if (now <snubbed)
snubbed =now -26; // odds are ...
return now -snubbed;
}
public void setSnubbed(boolean b)
{
if (!closing)
{
final long now =SystemTime.getCurrentTime();
if (!b)
{
if (snubbed !=0)
{
snubbed =0;
manager.decNbPeersSnubbed();
}
} else if (snubbed ==0)
{
snubbed =now;
manager.incNbPeersSnubbed();
}
}
}
public void setUploadHint(int spreadTime) { spreadTimeHint = spreadTime; }
public int getUploadHint() { return spreadTimeHint; }
public void setUniqueAnnounce(int _uniquePiece) { uniquePiece = _uniquePiece; }
public int getUniqueAnnounce() { return uniquePiece; }
public Object getData(String key) {
return( getUserData( key ));
}
public void
setData(String key, Object value)
{
setUserData( key, value );
}
/** To retreive arbitrary objects against a peer. */
public Object getUserData (Object key) {
try{
general_mon.enter();
if (data == null) return null;
return data.get(key);
}finally{
general_mon.exit();
}
}
/** To store arbitrary objects against a peer. */
public void setUserData (Object key, Object value) {
try{
general_mon.enter();
if (data == null) {
data = new LightHashMap();
}
if (value == null) {
if (data.containsKey(key)){
data.remove(key);
if ( data.size()==0 ){
data = null;
}
}
} else {
data.put(key, value);
}
}finally{
general_mon.exit();
}
}
public String
getIPHostName()
{
if ( ip_resolved == null ){
ip_resolved = ip;
ip_resolver_request = IPToHostNameResolver.addResolverRequest(
ip_resolved,
new IPToHostNameResolverListener()
{
public final void
IPResolutionComplete(
String res,
boolean ok )
{
ip_resolved = res;
}
});
}
return( ip_resolved );
}
private void cancelRequests()
{
if (!closing) { //cancel any unsent requests in the queue
final Message[] type ={new BTRequest(-1, -1, -1, other_peer_request_version)};
connection.getOutgoingMessageQueue().removeMessagesOfType(type, false);
}
if (requested !=null &&requested.size() >0) {
try{
requested_mon.enter();
if (!closing)
{ // may have unchoked us, gotten a request, then choked without filling it - snub them
// if they actually have data coming in, they'll be unsnubbed as soon as it writes
final long timeSinceGoodData =getTimeSinceGoodDataReceived();
if (timeSinceGoodData ==-1 ||timeSinceGoodData >60 *1000)
setSnubbed(true);
}
for (int i = requested.size() - 1; i >= 0; i--) {
final DiskManagerReadRequest request =(DiskManagerReadRequest) requested.remove(i);
manager.requestCanceled(request);
}
}finally{
requested_mon.exit();
}
}
}
public int
getMaxNbRequests()
{
return( -1 );
}
public int
getNbRequests() {
return requested.size();
}
/**
*
* @return may be null for performance purposes
*/
public List
getExpiredRequests() {
List result = null;
// this is frequently called, hence we operate without a monitor and
// take the hit of possible exceptions due to concurrent list
// modification (only out-of-bounds can occur)
try{
for (int i =requested.size() -1; i >=0; i--)
{
final DiskManagerReadRequest request = (DiskManagerReadRequest) requested.get(i);
if (request.isExpired()){
if ( result == null ){
result = new ArrayList();
}
result.add(request);
}
}
return( result );
}catch(Throwable e ){
return result;
}
}
private boolean hasBeenRequested( DiskManagerReadRequest request ) {
try{ requested_mon.enter();
return requested.contains( request );
}
finally{ requested_mon.exit(); }
}
protected void
removeRequest(
DiskManagerReadRequest request )
{
try{
requested_mon.enter();
requested.remove(request);
}finally{
requested_mon.exit();
}
final BTRequest msg = new BTRequest( request.getPieceNumber(), request.getOffset(), request.getLength(), other_peer_request_version );
connection.getOutgoingMessageQueue().removeMessage( msg, false );
msg.destroy();
}
private void
resetRequestsTime(final long now)
{
try{
requested_mon.enter();
final int requestedSize =requested.size();
for (int i =0; i <requestedSize; i++)
{
final DiskManagerReadRequest request =(DiskManagerReadRequest) requested.get(i);
if (request != null)
request.resetTime(now);
}
}finally{
requested_mon.exit();
}
}
public String toString() {
if( connection != null && connection.isConnected() ) {
return connection + (isTCP()?" [":"(UDP) [") + client+ "]";
}
return (isIncoming() ? "R: " : "L: ")+ ip + ":" + port + (isTCP()?" [":"(UDP) [") + client+ "]";
}
public String
getString()
{
return( toString());
}
public void doKeepAliveCheck() {
final long now =SystemTime.getCurrentTime();
final long wait_time =now -last_message_sent_time;
if( last_message_sent_time == 0 || wait_time < 0 ) {
last_message_sent_time =now; //don't send if brand new connection
return;
}
if( wait_time > 2*60*1000 ) { //2min keep-alive timer
sendKeepAlive();
last_message_sent_time =now; //not quite true, but we don't want to queue multiple keep-alives before the first is actually sent
}
}
public boolean doTimeoutChecks() {
//Timeouts for states PEPeerTransport.CONNECTION_PENDING and
//PEPeerTransport.CONNECTION_CONNECTING are handled by the ConnectDisconnectManager
//so we don't need to deal with them here.
if ( connection != null ){
connection.getOutgoingMessageQueue().setPriorityBoost( enable_upload_bias && !manager.isSeeding());
}
if ( fast_extension_enabled ){
checkAllowedFast();
}
final long now =SystemTime.getCurrentTime();
//make sure we time out stalled connections
if( connection_state == PEPeerTransport.CONNECTION_FULLY_ESTABLISHED ) {
if (last_message_received_time >now)
last_message_received_time =now;
if (last_data_message_received_time >now)
last_data_message_received_time =now;
if (now -last_message_received_time >5*60*1000
&&now -last_data_message_received_time >5*60*1000) { //5min timeout
// assume this is due to a network failure
// e.g. something that didn't close the TCP socket properly
// will attempt reconnect
closeConnectionInternally( "timed out while waiting for messages", false, true );
return true;
}
}
//ensure we dont get stuck in the handshaking phases
else if( connection_state == PEPeerTransport.CONNECTION_WAITING_FOR_HANDSHAKE ) {
if (connection_established_time >now)
connection_established_time =now;
else if (now - connection_established_time > 3*60*1000 ) { //3min timeout
closeConnectionInternally( "timed out while waiting for handshake" );
return true;
}
}
return false;
}
public void doPerformanceTuningCheck() {
Transport transport = connection.getTransport();
if( transport != null && peer_stats != null && outgoing_piece_message_handler != null ) {
//send speed -based tuning
final long send_rate = peer_stats.getDataSendRate() + peer_stats.getProtocolSendRate();
if( send_rate >= 3125000 ) { // 25 Mbit/s
transport.setTransportMode( Transport.TRANSPORT_MODE_TURBO );
outgoing_piece_message_handler.setRequestReadAhead( 256 );
}
else if( send_rate >= 1250000 ) { // 10 Mbit/s
transport.setTransportMode( Transport.TRANSPORT_MODE_TURBO );
outgoing_piece_message_handler.setRequestReadAhead( 128 );
}
else if( send_rate >= 125000 ) { // 1 Mbit/s
if( transport.getTransportMode() < Transport.TRANSPORT_MODE_FAST ) {
transport.setTransportMode( Transport.TRANSPORT_MODE_FAST );
}
outgoing_piece_message_handler.setRequestReadAhead( 32 );
}
else if( send_rate >= 62500 ) { // 500 Kbit/s
outgoing_piece_message_handler.setRequestReadAhead( 16 );
}
else if( send_rate >= 31250 ) { // 250 Kbit/s
outgoing_piece_message_handler.setRequestReadAhead( 8 );
}
else if( send_rate >= 12500 ) { // 100 Kbit/s
outgoing_piece_message_handler.setRequestReadAhead( 4 );
}
else {
outgoing_piece_message_handler.setRequestReadAhead( 2 );
}
//receive speed -based tuning
final long receive_rate = peer_stats.getDataReceiveRate() + peer_stats.getProtocolReceiveRate();
if( receive_rate >= 1250000 ) { // 10 Mbit/s
transport.setTransportMode( Transport.TRANSPORT_MODE_TURBO );
}
else if( receive_rate >= 125000 ) { // 1 Mbit/s
if( transport.getTransportMode() < Transport.TRANSPORT_MODE_FAST ) {
transport.setTransportMode( Transport.TRANSPORT_MODE_FAST );
}
}
}
}
public int getConnectionState() { return connection_state; }
public long getTimeSinceLastDataMessageReceived() {
if( last_data_message_received_time == -1 ) { //never received
return -1;
}
final long now =SystemTime.getCurrentTime();
if (last_data_message_received_time >now)
last_data_message_received_time =now; //time went backwards
return now -last_data_message_received_time;
}
public long getTimeSinceGoodDataReceived()
{
if (last_good_data_time ==-1)
return -1; // never received
final long now =SystemTime.getCurrentTime();
if (last_good_data_time >now)
last_good_data_time =now; //time went backwards
return now -last_good_data_time;
}
public long getTimeSinceLastDataMessageSent() {
if( last_data_message_sent_time == -1 ) { //never sent
return -1;
}
final long now =SystemTime.getCurrentTime();
if (last_data_message_sent_time >now)
last_data_message_sent_time =now; //time went backwards
return now -last_data_message_sent_time;
}
public long getTimeSinceConnectionEstablished() {
if( connection_established_time == 0 ) { //fudge it while the transport is being connected
return 0;
}
final long now =SystemTime.getCurrentTime();
if (connection_established_time >now)
connection_established_time =now;
return now -connection_established_time;
}
public int
getConsecutiveNoRequestCount()
{
return( consecutive_no_request_count );
}
public void
setConsecutiveNoRequestCount(
int num )
{
consecutive_no_request_count = num;
}
protected void decodeBTHandshake( BTHandshake handshake ) {
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID,
"Received handshake with reserved bytes: " +
ByteFormatter.nicePrint(handshake.getReserved(), false)));
PeerIdentityDataID my_peer_data_id = manager.getPeerIdentityDataID();
if(getConnectionState() == CONNECTION_FULLY_ESTABLISHED)
{
handshake.destroy();
closeConnectionInternally("peer sent another handshake after the initial connect");
}
if( !Arrays.equals( manager.getHash(), handshake.getDataHash() ) ) {
closeConnectionInternally( "handshake has wrong infohash" );
handshake.destroy();
return;
}
peer_id = handshake.getPeerId();
// Decode a client identification string from the given peerID
this.client_peer_id = this.client = StringInterner.intern(PeerClassifier.getClientDescription( peer_id ));
//make sure the client type is not banned
if( !PeerClassifier.isClientTypeAllowed( client ) ) {
closeConnectionInternally( client+ " client type not allowed to connect, banned" );
handshake.destroy();
return;
}
//make sure we are not connected to ourselves
if( Arrays.equals( manager.getPeerId(), peer_id ) ) {
manager.peerVerifiedAsSelf( this ); //make sure we dont do it again
closeConnectionInternally( "given peer id matches myself" );
handshake.destroy();
return;
}
//make sure we are not already connected to this peer
boolean sameIdentity = PeerIdentityManager.containsIdentity( my_peer_data_id, peer_id, getPort());
boolean sameIP = false;
//allow loopback connects for co-located proxy-based connections and testing
boolean same_allowed = COConfigurationManager.getBooleanParameter( "Allow Same IP Peers" ) || ip.equals( "127.0.0.1" );
if( !same_allowed ){
if( PeerIdentityManager.containsIPAddress( my_peer_data_id, ip )) {
sameIP = true;
}
}
if( sameIdentity ) {
boolean close = true;
if( connection.isLANLocal() ) { //this new connection is lan-local
PEPeerTransport existing = manager.getTransportFromIdentity( peer_id );
if( existing != null ){
String existing_ip = existing.getIp();
// normally we don't allow a lan-local to replace a lan-local connection. There is
// however one exception - where the existing connection comes from the gateway address
// and therefore actually denotes an effectively non-lan-local connection. Unfortunately
// we don't have a good way of finding the default gateway, so just go for ending in .1
if ( !existing.isLANLocal() ||
( existing_ip.endsWith( ".1" ) && !existing_ip.equals( ip ))) { //so drop the existing connection if it is an external (non lan-local) one
Debug.outNoStack( "Dropping existing non-lanlocal peer connection [" +existing+ "] in favour of [" + this + "]" );
manager.removePeer( existing );
close = false;
}
}
}
if( close ) {
closeConnectionInternally( "peer matches already-connected peer id" );
handshake.destroy();
return;
}
}
if( sameIP ) {
closeConnectionInternally( "peer matches already-connected IP address, duplicate connections not allowed" );
handshake.destroy();
return;
}
//make sure we haven't reached our connection limit
final int maxAllowed = manager.getMaxNewConnectionsAllowed();
if ( maxAllowed ==0 &&
!manager.doOptimisticDisconnect( isLANLocal(), isPriorityConnection()))
{
final String msg = "too many existing peer connections [p" +
PeerIdentityManager.getIdentityCount( my_peer_data_id )
+"/g" +PeerIdentityManager.getTotalIdentityCount()
+", pmx" +PeerUtils.MAX_CONNECTIONS_PER_TORRENT+ "/gmx"
+PeerUtils.MAX_CONNECTIONS_TOTAL+"/dmx" + manager.getMaxConnections()+ "]";
//System.out.println( msg );
closeConnectionInternally( msg );
handshake.destroy();
return;
}
try{
closing_mon.enter();
if( closing ){
final String msg = "connection already closing";
closeConnectionInternally( msg );
handshake.destroy();
return;
}
if ( !PeerIdentityManager.addIdentity( my_peer_data_id, peer_id, getPort(), ip )){
closeConnectionInternally( "peer matches already-connected peer id" );
handshake.destroy();
return;
}
identityAdded = true;
}finally{
closing_mon.exit();
}
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID, "In: has sent their handshake"));
// Let's store the reserved bits somewhere so they can be examined later (externally).
handshake_reserved_bytes = handshake.getReserved();
/*
* Waiting until we've received the initiating-end's full handshake, before sending back our own,
* really should be the "proper" behavior. However, classic BT trackers running NAT checking will
* only send the first 48 bytes (up to infohash) of the peer handshake, skipping peerid, which means
* we'll never get their complete handshake, and thus never reply, which causes the NAT check to fail.
* So, we need to send our handshake earlier, after we've verified the infohash.
*
if( incoming ) { //wait until we've received their handshake before sending ours
sendBTHandshake();
}
*/
this.ml_dht_enabled = (handshake_reserved_bytes[7] & 0x01 ) == 1;
fast_extension_enabled = BTHandshake.FAST_EXTENSION_ENABLED && (handshake_reserved_bytes[7] & 0x04) != 0;
messaging_mode = decideExtensionProtocol(handshake);
//extended protocol processing
if (messaging_mode == MESSAGING_AZMP) {
/**
* We log when a non-Azureus client claims to support extended messaging...
* Obviously other Azureus clients do, so there's no point logging about them!
*/
if (Logger.isEnabled() && client.indexOf("Azureus") == -1) {
Logger.log(new LogEvent(this, LOGID, "Handshake claims extended AZ "
+ "messaging support... enabling AZ mode."));
}
// Ignore the handshake setting - wait for the AZHandshake to indicate
// support instead.
this.ml_dht_enabled = false;
Transport transport = connection.getTransport();
int padding_mode;
if ( transport.isEncrypted()){
if ( transport.isTCP()){
padding_mode = AZMessageEncoder.PADDING_MODE_NORMAL;
}else{
padding_mode = AZMessageEncoder.PADDING_MODE_MINIMAL;
}
}else{
padding_mode = AZMessageEncoder.PADDING_MODE_NONE;
}
connection.getIncomingMessageQueue().setDecoder(new AZMessageDecoder());
connection.getOutgoingMessageQueue().setEncoder(new AZMessageEncoder(padding_mode));
// We will wait until we get the Az handshake before considering the connection
// initialised.
this.sendAZHandshake();
handshake.destroy();
}
else if (messaging_mode == MESSAGING_LTEP) {
if (Logger.isEnabled()) {
Logger.log(new LogEvent(this, LOGID, "Enabling LT extension protocol support..."));
}
connection.getIncomingMessageQueue().setDecoder(new LTMessageDecoder());
connection.getOutgoingMessageQueue().setEncoder(new LTMessageEncoder(this));
generateSessionId();
/**
* We don't need to wait for the LT handshake, nor do we require it, nor
* does it matter if the LT handshake comes later, nor does it matter if
* it we receive it repeatedly. So there - we can initialise the connection
* right now. :P
*/
this.initPostConnection(handshake);
this.sendLTHandshake();
}
else {
this.client = ClientIdentifier.identifyBTOnly(this.client_peer_id, this.handshake_reserved_bytes);
connection.getIncomingMessageQueue().getDecoder().resumeDecoding();
this.initPostConnection(handshake);
}
}
private int decideExtensionProtocol(BTHandshake handshake) {
boolean supports_azmp = (handshake.getReserved()[0] & 128) == 128;
boolean supports_ltep = (handshake.getReserved()[5] & 16) == 16;
if (!supports_azmp) {
if (supports_ltep) {
if (!manager.isExtendedMessagingEnabled()) {
if (Logger.isEnabled()) {
Logger.log(new LogEvent(this, LOGID, "Ignoring peer's LT extension protocol support,"
+ " as disabled for this download."));
}
return MESSAGING_BT_ONLY; // LTEP is supported, but disabled.
}
return MESSAGING_LTEP; // LTEP is supported.
}
return MESSAGING_BT_ONLY; // LTEP isn't supported.
}
if (!supports_ltep) {
// Check if it is AZMP enabled.
if(!manager.isExtendedMessagingEnabled()) {
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID, "Ignoring peer's extended AZ messaging support,"
+ " as disabled for this download."));
return MESSAGING_BT_ONLY;
}
// Check if the client is misbehaving...
else if( client.indexOf( "Plus!" ) != -1) {
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID, "Handshake mistakingly indicates"
+ " extended AZ messaging support...ignoring."));
return MESSAGING_BT_ONLY;
}
return MESSAGING_AZMP;
}
boolean enp_major_bit = (handshake.getReserved()[5] & 2) == 2;
boolean enp_minor_bit = (handshake.getReserved()[5] & 1) == 1;
// Only enable one of the blocks below.
String their_ext_preference = ((enp_major_bit == enp_minor_bit) ? "Force " : "Prefer ") + ((enp_major_bit) ? "AZMP" : "LTEP");
// Force AZMP block.
String our_ext_preference = "Force AZMP";
boolean use_azmp = enp_major_bit || enp_minor_bit; // Anything other than Force LTEP, then we force AZMP to be used.
boolean we_decide = use_azmp;
// Prefer AZMP block (untested).
/*
String our_ext_preference = "Prefer AZMP";
boolean use_azmp = enp_major_bit; // Requires other client to prefer or force AZMP.
boolean we_decide = use_azmp && !enp_minor_bit; // We decide only if we are using AZMP and the other client didn't force it.
*/
// Prefer LTEP block (untested).
/*
String our_ext_preference = "Prefer LTEP";
boolean use_azmp = enp_major_bit && enp_minor_bit; // Only use it Force AZMP is enabled.
boolean we_decide = enp_minor_bit && !use_azmp; // We decide only if we are using LTEP and the other client didn't force it.
*/
if (Logger.isEnabled()) {
String msg = "Peer supports both AZMP and LTEP: ";
msg += "\"" + our_ext_preference + "\"" + (we_decide ? ">" : "<") + ((our_ext_preference.equals(their_ext_preference)) ? "= " : " ");
msg += "\"" + their_ext_preference + "\" - using " + (use_azmp ? "AZMP" : "LTEP");
Logger.log(new LogEvent(this, LOGID, msg));
}
return (use_azmp) ? MESSAGING_AZMP : MESSAGING_LTEP;
}
protected void decodeLTHandshake(LTHandshake handshake) {
String lt_handshake_name = handshake.getClientName();
if (lt_handshake_name != null) {
this.client_handshake = StringInterner.intern(lt_handshake_name);
this.client = StringInterner.intern(ClientIdentifier.identifyLTEP(this.client_peer_id, this.client_handshake, this.peer_id));
}
if(handshake.getTCPListeningPort() > 0 )
{
// Only use crypto if it was specifically requested. Not sure what the default
// should be if they haven't indicated...
Boolean crypto_requested = handshake.isCryptoRequested();
byte handshake_type = (crypto_requested != null && crypto_requested.booleanValue()) ? PeerItemFactory.HANDSHAKE_TYPE_CRYPTO : PeerItemFactory.HANDSHAKE_TYPE_PLAIN;
tcp_listen_port = handshake.getTCPListeningPort();
peer_item_identity = PeerItemFactory.createPeerItem(
ip, tcp_listen_port,
PeerItem.convertSourceID(peer_source),
handshake_type,
udp_listen_port, // probably none
crypto_level,
0
);
}
if(handshake.isUploadOnly())
{
relativeSeeding |= RELATIVE_SEEDING_UPLOAD_ONLY_INDICATED;
checkSeed();
}
if(AddressUtils.isGlobalAddressV6(handshake.getIPv6()))
alternativeAddress = handshake.getIPv6();
LTMessageEncoder encoder = (LTMessageEncoder)connection.getOutgoingMessageQueue().getEncoder();
encoder.updateSupportedExtensions(handshake.getExtensionMapping());
this.ut_pex_enabled = UTPeerExchange.ENABLED && encoder.supportsUTPEX();
/**
* Grr... this is one thing which I'm sure I had figured out much better than it is here...
* Basically, we "initialise" the connection at the BT handshake stage, because the LT handshake
* is mandatory or required to come first (unlike the AZ one).
*
* But when we receive an LT handshake, we have to "initialise" it like we did previously, because
* we may have to set the internals up to indicate if PEX is supported.
*
* I'm not entirely sure this method is meant to be called more than once, and I'm less convinced
* that it's safe to do it repeatedly over the lifetime of a properly-initialised, actually-doing-stuff
* connection... but I'll worry about that later.
*/
this.doPostHandshakeProcessing();
handshake.destroy();
}
protected void decodeAZHandshake(AZHandshake handshake) {
if(getConnectionState() == CONNECTION_FULLY_ESTABLISHED)
{
handshake.destroy();
closeConnectionInternally("peer sent another az-handshake after the intial connect");
}
this.client_handshake = StringInterner.intern(handshake.getClient());
this.client_handshake_version = StringInterner.intern(handshake.getClientVersion());
this.client = StringInterner.intern(ClientIdentifier.identifyAZMP(this.client_peer_id, client_handshake, client_handshake_version, this.peer_id));
if (handshake.getTCPListenPort() > 0)
{ // use the ports given in handshake
tcp_listen_port = handshake.getTCPListenPort();
udp_listen_port = handshake.getUDPListenPort();
udp_non_data_port = handshake.getUDPNonDataListenPort();
final byte type = handshake.getHandshakeType() == AZHandshake.HANDSHAKE_TYPE_CRYPTO ? PeerItemFactory.HANDSHAKE_TYPE_CRYPTO : PeerItemFactory.HANDSHAKE_TYPE_PLAIN;
// remake the id using the peer's remote listen port instead of
// their random local port
peer_item_identity = PeerItemFactory.createPeerItem(ip, tcp_listen_port, PeerItem.convertSourceID(peer_source), type, udp_listen_port, crypto_level, 0);
}
if(AddressUtils.isGlobalAddressV6(handshake.getIPv6()))
alternativeAddress = handshake.getIPv6();
if(handshake.getReconnectSessionID() != null)
{
if (Logger.isEnabled()) {
Logger.log(new LogEvent(this, LOGID, LogEvent.LT_INFORMATION, "received reconnect request ID: "+handshake.getReconnectSessionID().toBase32String()));
}
checkForReconnect(handshake.getReconnectSessionID());
}
if(handshake.getRemoteSessionID() != null)
peerSessionID = handshake.getRemoteSessionID();
if (handshake.isUploadOnly())
{
relativeSeeding |= RELATIVE_SEEDING_UPLOAD_ONLY_INDICATED;
checkSeed();
}
String[] supported_message_ids = handshake.getMessageIDs();
byte[] supported_message_versions = handshake.getMessageVersions();
//find mutually available message types
final ArrayList messages = new ArrayList();
for (int i = 0; i < handshake.getMessageIDs().length; i++)
{
Message msg = MessageManager.getSingleton().lookupMessage(supported_message_ids[i]);
if (msg != null)
{ // mutual support!
messages.add(msg);
String id = msg.getID();
byte supported_version = supported_message_versions[i];
// we can use == safely
if (id == BTMessage.ID_BT_BITFIELD)
other_peer_bitfield_version = supported_version;
else if (id == BTMessage.ID_BT_CANCEL)
other_peer_cancel_version = supported_version;
else if (id == BTMessage.ID_BT_CHOKE)
other_peer_choke_version = supported_version;
else if (id == BTMessage.ID_BT_HANDSHAKE)
other_peer_handshake_version = supported_version;
else if (id == BTMessage.ID_BT_HAVE)
other_peer_bt_have_version = supported_version;
else if (id == BTMessage.ID_BT_INTERESTED)
other_peer_interested_version = supported_version;
else if (id == BTMessage.ID_BT_KEEP_ALIVE)
other_peer_keep_alive_version = supported_version;
else if (id == BTMessage.ID_BT_PIECE)
other_peer_piece_version = supported_version;
else if (id == BTMessage.ID_BT_UNCHOKE)
other_peer_unchoke_version = supported_version;
else if (id == BTMessage.ID_BT_UNINTERESTED)
other_peer_uninterested_version = supported_version;
else if (id == BTMessage.ID_BT_REQUEST)
other_peer_request_version = supported_version;
else if (id == BTMessage.ID_BT_SUGGEST_PIECE)
other_peer_suggest_piece_version = supported_version;
else if (id == BTMessage.ID_BT_HAVE_ALL)
other_peer_have_all_version = supported_version;
else if (id == BTMessage.ID_BT_HAVE_NONE)
other_peer_have_none_version = supported_version;
else if (id == BTMessage.ID_BT_REJECT_REQUEST)
other_peer_reject_request_version = supported_version;
else if (id == BTMessage.ID_BT_ALLOWED_FAST)
other_peer_allowed_fast_version = supported_version;
else if (id == AZMessage.ID_AZ_PEER_EXCHANGE)
other_peer_pex_version = supported_version;
else if (id == AZMessage.ID_AZ_REQUEST_HINT)
other_peer_az_request_hint_version = supported_version;
else if (id == AZMessage.ID_AZ_HAVE)
other_peer_az_have_version = supported_version;
else if (id == AZMessage.ID_AZ_BAD_PIECE)
other_peer_az_bad_piece_version = supported_version;
else if (id == AZMessage.ID_AZ_STAT_REQUEST)
other_peer_az_stats_request_version = supported_version;
else if (id == AZMessage.ID_AZ_STAT_REPLY)
other_peer_az_stats_reply_version = supported_version;
else if (id == BTMessage.ID_BT_DHT_PORT)
this.ml_dht_enabled = true;
else
{
// we expect unmatched ones here at the moment as we're not
// dealing with them yet or they don't make sense.
// for example AZVER
}
}
}
supported_messages = (Message[]) messages.toArray(new Message[messages.size()]);
if(outgoing_piece_message_handler != null){
outgoing_piece_message_handler.setPieceVersion(other_peer_piece_version);
}
if ( outgoing_have_message_aggregator != null ){
outgoing_have_message_aggregator.setHaveVersion(other_peer_bt_have_version, other_peer_az_have_version);
}
this.initPostConnection(handshake);
}
private void initPostConnection(Message handshake) {
changePeerState(PEPeer.TRANSFERING);
connection_state = PEPeerTransport.CONNECTION_FULLY_ESTABLISHED;
sendBitField();
handshake.destroy();
addAvailability();
sendMainlineDHTPort();
}
protected void
decodeHaveAll(
BTHaveAll have_all )
{
have_all.destroy();
received_bitfield = true;
try{
closing_mon.enter();
if ( !closing ){
final BitFlags tempHavePieces;
if ( peerHavePieces ==null ){
tempHavePieces = new BitFlags(nbPieces);
}else{
tempHavePieces = peerHavePieces;
removeAvailability();
}
tempHavePieces.setAll();
for ( int i=0; i<nbPieces; i++ ){
manager.updateSuperSeedPiece( this, i );
}
peerHavePieces = tempHavePieces;
addAvailability();
checkSeed();
checkInterested();
}
}finally{
closing_mon.exit();
}
}
protected void
decodeHaveNone(
BTHaveNone have_none )
{
have_none.destroy();
received_bitfield = true;
try{
closing_mon.enter();
if ( !closing ){
final BitFlags tempHavePieces;
if ( peerHavePieces ==null ){
tempHavePieces = new BitFlags(nbPieces);
}else{
tempHavePieces = peerHavePieces;
removeAvailability();
}
tempHavePieces.clear();
peerHavePieces = tempHavePieces;
//some of these kinda pointless but whatever
addAvailability();
checkSeed();
checkInterested();
checkFast( tempHavePieces );
}
}finally{
closing_mon.exit();
}
}
protected void decodeBitfield( BTBitfield bitfield )
{
received_bitfield = true;
final DirectByteBuffer field =bitfield.getBitfield();
final byte[] dataf =new byte[(nbPieces +7) /8];
if( field.remaining( DirectByteBuffer.SS_PEER ) < dataf.length ) {
final String error = toString() + " has sent invalid Bitfield: too short [" +field.remaining( DirectByteBuffer.SS_PEER )+ "<" +dataf.length+ "]";
Debug.out( error );
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID, LogEvent.LT_ERROR, error ));
bitfield.destroy();
return;
}
field.get( DirectByteBuffer.SS_PEER, dataf );
try{
closing_mon.enter();
if (closing)
bitfield.destroy();
else
{
final BitFlags tempHavePieces;
if (peerHavePieces ==null)
{
tempHavePieces =new BitFlags(nbPieces);
} else
{
tempHavePieces =peerHavePieces;
removeAvailability();
}
for (int i =0; i <nbPieces; i++)
{
final int index =i /8;
final int bit =7 -(i %8);
final byte bData =dataf[index];
final byte b =(byte) (bData >>bit);
if ((b &0x01) ==1)
{
tempHavePieces.set(i);
manager.updateSuperSeedPiece(this,i);
}
}
bitfield.destroy();
peerHavePieces =tempHavePieces;
addAvailability();
checkSeed();
checkInterested();
checkFast( tempHavePieces );
}
}
finally{
closing_mon.exit();
}
}
public void
setSuspendedLazyBitFieldEnabled(
boolean enable )
{
manual_lazy_bitfield_control = enable;
if ( !enable ){
int[] pending = manual_lazy_haves;
manual_lazy_haves = null;
if ( pending != null ){
sendLazyHaves( pending, true );
}
}
}
protected void decodeMainlineDHTPort(BTDHTPort port) {
int i_port = port.getDHTPort();
port.destroy();
if (!this.ml_dht_enabled) {return;}
MainlineDHTProvider provider = getDHTProvider();
if (provider == null) {return;}
try {provider.notifyOfIncomingPort(getIp(), i_port);}
catch (Throwable t) {Debug.printStackTrace(t);}
}
protected void decodeChoke( BTChoke choke ) {
choke.destroy();
if (!really_choked_by_other_peer)
{
really_choked_by_other_peer = true;
calculatePiecePriorities();
cancelRequests();
}
}
protected void decodeUnchoke( BTUnchoke unchoke ) {
unchoke.destroy();
if (really_choked_by_other_peer)
{
really_choked_by_other_peer = false;
calculatePiecePriorities();
}
}
protected void
decodeInterested(
BTInterested interested )
{
interested.destroy();
// Don't allow known seeds to be interested in us
other_peer_interested_in_me = !(isSeed() || isRelativeSeed());
if ( other_peer_interested_in_me &&
fast_unchoke_new_peers &&
isChokedByMe() &&
getData( "fast_unchoke_done" ) == null ){
setData( "fast_unchoke_done", "" );
sendUnChoke();
}
}
protected void decodeUninterested( BTUninterested uninterested ) {
uninterested.destroy();
other_peer_interested_in_me = false;
//force send any pending haves in case one of them would make the other peer interested again
if( outgoing_have_message_aggregator != null ) {
outgoing_have_message_aggregator.forceSendOfPending();
}
}
protected void decodeHave( BTHave have ) {
final int pieceNumber =have.getPieceNumber();
have.destroy();
if ((pieceNumber >=nbPieces) ||(pieceNumber <0)) {
closeConnectionInternally("invalid pieceNumber: " +pieceNumber);
return;
}
if (closing)
return;
if (peerHavePieces ==null)
peerHavePieces =new BitFlags(nbPieces);
if (!peerHavePieces.flags[pieceNumber])
{
if (!interested_in_other_peer &&diskManager.isInteresting(pieceNumber))
{
connection.getOutgoingMessageQueue().addMessage(new BTInterested(other_peer_interested_version), false);
interested_in_other_peer =true;
}
peerHavePieces.set(pieceNumber);
final int pieceLength =manager.getPieceLength(pieceNumber);
manager.havePiece(pieceNumber, pieceLength, this);
checkSeed(); // maybe a seed using lazy bitfield, or suddenly became a seed;
other_peer_interested_in_me &= !(isSeed() || isRelativeSeed()); // never consider seeds interested
peer_stats.hasNewPiece(pieceLength);
}
}
protected void
decodeAZHave(
AZHave have )
{
final int[] pieceNumbers = have.getPieceNumbers();
have.destroy();
if ( closing ){
return;
}
if ( peerHavePieces == null ){
peerHavePieces = new BitFlags( nbPieces );
}
boolean send_interested = false;
boolean new_have = false;
for (int i=0;i<pieceNumbers.length;i++){
int pieceNumber = pieceNumbers[i];
if ((pieceNumber >= nbPieces) ||(pieceNumber <0)) {
closeConnectionInternally("invalid pieceNumber: " +pieceNumber);
return;
}
if ( !peerHavePieces.flags[pieceNumber]){
new_have = true;
if ( !( send_interested || interested_in_other_peer ) &&
diskManager.isInteresting(pieceNumber)){
send_interested = true;
}
peerHavePieces.set(pieceNumber);
final int pieceLength =manager.getPieceLength(pieceNumber);
manager.havePiece(pieceNumber, pieceLength, this);
peer_stats.hasNewPiece(pieceLength);
}
}
if ( new_have ){
checkSeed(); // maybe a seed using lazy bitfield, or suddenly became a seed;
other_peer_interested_in_me &= !(isSeed() && isRelativeSeed()); // never consider seeds interested
}
if ( send_interested ){
connection.getOutgoingMessageQueue().addMessage(new BTInterested(other_peer_interested_version), false);
interested_in_other_peer = true;
}
}
protected long
getBytesDownloaded()
{
if (peerHavePieces ==null ||peerHavePieces.flags.length ==0)
return 0;
final long total_done;
if ( peerHavePieces.flags[nbPieces-1] ){
total_done = ((long)(peerHavePieces.nbSet -1) *diskManager.getPieceLength()) +diskManager.getPieceLength( nbPieces -1 );
}else{
total_done =(long)peerHavePieces.nbSet *diskManager.getPieceLength();
}
return( Math.min( total_done, diskManager.getTotalLength()));
}
public long
getBytesRemaining()
{
return( diskManager.getTotalLength() - getBytesDownloaded());
}
public void
sendBadPiece(
int piece_number )
{
if ( bad_piece_supported ){
AZBadPiece bp = new AZBadPiece( piece_number, other_peer_az_bad_piece_version );
connection.getOutgoingMessageQueue().addMessage( bp, false );
}
}
protected void
decodeAZBadPiece(
AZBadPiece bad_piece )
{
final int piece_number = bad_piece.getPieceNumber();
bad_piece.destroy();
manager.badPieceReported( this, piece_number );
}
public void
sendStatsRequest(
Map request )
{
if ( stats_request_supported ){
AZStatRequest sr = new AZStatRequest( request, other_peer_az_stats_request_version );
connection.getOutgoingMessageQueue().addMessage( sr, false );
}
}
protected void
decodeAZStatsRequest(
AZStatRequest request )
{
Map req = request.getRequest();
request.destroy();
manager.statsRequest( this, req );
}
public void
sendStatsReply(
Map reply )
{
if ( stats_reply_supported ){
AZStatReply sr = new AZStatReply( reply, other_peer_az_stats_reply_version );
connection.getOutgoingMessageQueue().addMessage( sr, false );
}
}
protected void
decodeAZStatsReply(
AZStatReply reply )
{
Map rep = reply.getReply();
reply.destroy();
manager.statsReply( this, rep );
}
protected void decodeRequest( BTRequest request ) {
final int number = request.getPieceNumber();
final int offset = request.getPieceOffset();
final int length = request.getLength();
request.destroy();
if( !manager.validateReadRequest( this, number, offset, length ) ) {
closeConnectionInternally( "request for piece #" + number + ":" + offset + "->" + (offset + length -1) + " is an invalid request" );
return;
}
if ( manager.getHiddenPiece() == number ){
closeConnectionInternally( "request for piece #" + number + " is invalid as piece is hidden" );
return;
}
boolean request_ok = false;
if ( choking_other_peer ){
try{
general_mon.enter();
int[][] pieces = (int[][])getUserData( KEY_ALLOWED_FAST_SENT );
if ( pieces != null ){
for (int i=0;i<pieces.length;i++){
if ( pieces[i][0] == number ){
if ( pieces[i][1] >= length ){
pieces[i][1] -= length;
if ( DEBUG_FAST ){
System.out.println( "Permitting fast-allowed request for " + number + "/" + offset + "/" + length + " to " + getIp());
}
request_ok = true;
createPieceMessageHandler();
break;
}
}
}
}
}finally{
general_mon.exit();
}
}else{
request_ok = true;
}
if ( request_ok ){
if ( outgoing_piece_message_handler == null || !outgoing_piece_message_handler.addPieceRequest( number, offset, length )){
sendRejectRequest( number, offset, length );
}
allowReconnect = true;
}else{
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID, "decodeRequest(): peer request for piece #"
+ number + ":" + offset + "->" + (offset + length -1)
+ " ignored as peer is currently choked."));
sendRejectRequest( number, offset, length );
}
}
public void
sendRejectRequest(
DiskManagerReadRequest request )
{
sendRejectRequest( request.getPieceNumber(), request.getOffset(), request.getLength());
}
private void
sendRejectRequest(
int number,
int offset,
int length )
{
if ( fast_extension_enabled && !closing ){
// System.out.println( "Sending reject request " + number + "/" + offset + "/" + length + " to " + getIp());
BTRejectRequest reject = new BTRejectRequest( number, offset, length, other_peer_reject_request_version );
connection.getOutgoingMessageQueue().addMessage( reject, false );
}
}
protected void decodePiece( BTPiece piece ) {
final int pieceNumber = piece.getPieceNumber();
final int offset = piece.getPieceOffset();
final DirectByteBuffer payload = piece.getPieceData();
final int length = payload.remaining( DirectByteBuffer.SS_PEER );
/*
if ( AEDiagnostics.CHECK_DUMMY_FILE_DATA ){
int pos = payload.position( DirectByteBuffer.SS_PEER );
long off = ((long)number) * getControl().getPieceLength(0) + offset;
for (int i=0;i<length;i++){
byte v = payload.get( DirectByteBuffer.SS_PEER );
if ((byte)off != v ){
System.out.println( "piece: read is bad at " + off + ": expected = " + (byte)off + ", actual = " + v );
break;
}
off++;
}
payload.position( DirectByteBuffer.SS_PEER, pos );
}
*/
final Object error_msg =
new Object()
{
public final String
toString()
{
return( "decodePiece(): Peer has sent piece #" + pieceNumber + ":" + offset + "->" + (offset + length -1) + ", " );
}
};
if( !manager.validatePieceReply( this, pieceNumber, offset, payload ) ) {
peer_stats.bytesDiscarded( length );
manager.discarded( this, length );
requests_discarded++;
printRequestStats();
piece.destroy();
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID, LogEvent.LT_ERROR,
error_msg
+"but piece block discarded as invalid."));
return;
}
if ( DEBUG_FAST ){
if ( fast_extension_enabled && really_choked_by_other_peer ){
System.out.println( "Received allow-fast piece for " + pieceNumber + "/" + offset + "/" + length + " from " + getIp());
}
}
final DiskManagerReadRequest request = manager.createDiskManagerRequest( pieceNumber, offset, length );
boolean piece_error = true;
if( hasBeenRequested( request ) ) { //from active request
removeRequest( request );
final long now =SystemTime.getCurrentTime();
resetRequestsTime(now);
if( manager.isWritten( pieceNumber, offset ) ) { //oops, looks like this block has already been written
peer_stats.bytesDiscarded( length );
manager.discarded( this, length );
if( manager.isInEndGameMode() ) { //we're probably in end-game mode then
if (last_good_data_time !=-1 &&now -last_good_data_time <=60 *1000)
setSnubbed(false);
last_good_data_time =now;
requests_discarded_endgame++;
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LogIDs.PIECES, LogEvent.LT_INFORMATION,
error_msg
+"but piece block ignored as already written in end-game mode."));
}
else {
// if they're not snubbed, then most likely this peer got a re-request after some other peer
// snubbed themselves, and the slow peer finially finished the piece, but before this peer did
// so give credit to this peer anyway for having delivered a block at this time
if (!isSnubbed())
last_good_data_time =now;
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LogIDs.PIECES, LogEvent.LT_WARNING,
error_msg
+"but piece block discarded as already written."));
requests_discarded++;
}
printRequestStats();
}
else { //successfully received block!
manager.writeBlock( pieceNumber, offset, payload, this, false);
if (last_good_data_time !=-1 &&now -last_good_data_time <=60 *1000)
setSnubbed( false );
last_good_data_time =now;
requests_completed++;
piece_error = false; //dont destroy message, as we've passed the payload on to the disk manager for writing
}
}
else { //initial request may have already expired, but check if we can use the data anyway
if( !manager.isWritten( pieceNumber, offset ) ) {
final boolean ever_requested;
try{ recent_outgoing_requests_mon.enter();
ever_requested = recent_outgoing_requests.containsKey( request );
}
finally{ recent_outgoing_requests_mon.exit(); }
if( ever_requested ) { //security-measure: we dont want to be accepting any ol' random block
manager.writeBlock( pieceNumber, offset, payload, this, true);
final long now =SystemTime.getCurrentTime();
if (last_good_data_time !=-1 &&now -last_good_data_time <=60 *1000)
setSnubbed(false);
resetRequestsTime(now);
last_good_data_time =now;
requests_recovered++;
printRequestStats();
piece_error = false; //dont destroy message, as we've passed the payload on to the disk manager for writing
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LogIDs.PIECES, LogEvent.LT_INFORMATION,
error_msg
+"expired piece block data recovered as useful."));
}
else {
System.out.println( "[" +client+ "]" +error_msg + "but expired piece block discarded as never requested." );
peer_stats.bytesDiscarded( length );
manager.discarded( this, length );
requests_discarded++;
printRequestStats();
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LogIDs.PIECES, LogEvent.LT_ERROR,
error_msg
+"but expired piece block discarded as never requested."));
}
}
else {
peer_stats.bytesDiscarded( length );
manager.discarded( this, length );
requests_discarded++;
printRequestStats();
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LogIDs.PIECES, LogEvent.LT_WARNING,
error_msg
+"but expired piece block discarded as already written."));
}
}
if( piece_error )
piece.destroy();
else
allowReconnect = true;
}
protected void
decodeCancel(
BTCancel cancel )
{
int number = cancel.getPieceNumber();
int offset = cancel.getPieceOffset();
int length = cancel.getLength();
cancel.destroy();
if(outgoing_piece_message_handler != null)
outgoing_piece_message_handler.removePieceRequest( number, offset, length );
}
protected void
decodeRejectRequest(
BTRejectRequest reject )
{
int number = reject.getPieceNumber();
int offset = reject.getPieceOffset();
int length = reject.getLength();
reject.destroy();
final DiskManagerReadRequest request = manager.createDiskManagerRequest( number, offset, length );
if ( hasBeenRequested( request )){
removeRequest( request );
manager.requestCanceled( request );
// if fast-allowed block rejected, remove from set so we don't re-request it
try{
general_mon.enter();
List<Integer> pieces = (List<Integer>)getUserData( KEY_ALLOWED_FAST_RECEIVED );
if ( pieces != null ){
pieces.remove( new Integer( number ));
if ( pieces.size() == 0 ){
setUserData( KEY_ALLOWED_FAST_RECEIVED, null );
}
}
int[] priorities = piece_priority_offsets;
if ( priorities != null ){
priorities[number] = Integer.MIN_VALUE;
}
calculatePiecePriorities();
}finally{
general_mon.exit();
}
}
}
private void
decodeAllowedFast(
BTAllowedFast allowed )
{
int piece = allowed.getPieceNumber();
allowed.destroy();
if ( piecePicker.getNbPiecesDone() > ALLOWED_FAST_OTHER_PEER_PIECE_MAX ){
// we have too many pieces already, ignore
return;
}
if ( DEBUG_FAST ){
System.out.println( "Received allow-fast " + piece + " from " + getIp());
}
try{
general_mon.enter();
List<Integer> pieces = (List<Integer>)getUserData( KEY_ALLOWED_FAST_RECEIVED );
if ( pieces == null ){
pieces = new ArrayList<Integer>( ALLOWED_FAST_OTHER_PEER_PIECE_MAX );
setUserData( KEY_ALLOWED_FAST_RECEIVED, pieces );
}
if ( pieces.size() < ALLOWED_FAST_OTHER_PEER_PIECE_MAX * 2 ){
Integer i = new Integer( piece );
if ( !pieces.contains( i ) && i >=0 && i < nbPieces ){
pieces.add( i );
calculatePiecePriorities();
}
}
}finally{
general_mon.exit();
}
}
private void
sendAllowFast(
int number )
{
if ( fast_extension_enabled ){
if ( DEBUG_FAST ){
System.out.println( "Sending allow-fast " + number + " to " + getIp());
}
BTAllowedFast af = new BTAllowedFast( number, other_peer_allowed_fast_version );
connection.getOutgoingMessageQueue().addMessage( af, false );
}
}
protected void
calculatePiecePriorities()
{
try{
general_mon.enter();
if ( really_choked_by_other_peer ){
List<Integer> pieces = (List<Integer>)getUserData( KEY_ALLOWED_FAST_RECEIVED );
if ( pieces == null ){
effectively_choked_by_other_peer = true;
piece_priority_offsets = null;
}else{
int[] priorities = piece_priority_offsets;
if ( priorities == null ){
priorities = new int[nbPieces];
Arrays.fill( priorities, Integer.MIN_VALUE );
}
for ( int i: pieces ){
priorities[i] = 0;
}
piece_priority_offsets = priorities;
if ( effectively_choked_by_other_peer ){
effectively_choked_by_other_peer = false;
effectively_unchoked_time = SystemTime.getMonotonousTime();
}
}
}else{
if ( effectively_choked_by_other_peer ){
effectively_choked_by_other_peer = false;
effectively_unchoked_time = SystemTime.getMonotonousTime();
}
piece_priority_offsets = null;
}
}finally{
general_mon.exit();
}
}
protected void
checkFast(
BitFlags flags )
{
if ( fast_extension_enabled &&
!(isSeed() || isRelativeSeed()) &&
PeerClassifier.fullySupportsFE( client_peer_id )){
// if already has enough pieces then bail
if ( flags.nbSet >= ALLOWED_FAST_OTHER_PEER_PIECE_MAX ){
return;
}
int[][] pieces;
try{
general_mon.enter();
pieces = (int[][])getUserData( KEY_ALLOWED_FAST_SENT );
if ( pieces == null ){
List<Integer> l_pieces = generateFastSet( ALLOWED_FAST_PIECE_OFFERED_NUM );
pieces = new int[l_pieces.size()][2];
int piece_size = diskManager.getPieceLength();
for ( int i=0;i<l_pieces.size(); i++ ){
int piece_number = l_pieces.get(i);
pieces[i] = new int[]{ piece_number, piece_size*2 };
}
setUserData( KEY_ALLOWED_FAST_SENT, pieces );
}
}finally{
general_mon.exit();
}
for ( int i=0;i<pieces.length;i++){
int piece_number = pieces[i][0];
if ( !flags.flags[ piece_number ] ){
sendAllowFast( piece_number );
}
}
}
}
private void
checkAllowedFast()
{
try{
general_mon.enter();
if ( piecePicker.getNbPiecesDone() > ALLOWED_FAST_OTHER_PEER_PIECE_MAX ){
List<Integer> pieces = (List<Integer>)getUserData( KEY_ALLOWED_FAST_RECEIVED );
if ( pieces != null ){
if ( DEBUG_FAST ){
System.out.println( "Clearing down fast received for " + getIp());
}
setUserData( KEY_ALLOWED_FAST_RECEIVED, null );
calculatePiecePriorities();
}
}
BitFlags flags = peerHavePieces;
if ( flags != null && flags.nbSet >= ALLOWED_FAST_OTHER_PEER_PIECE_MAX ){
int[][] pieces = (int[][])getUserData( KEY_ALLOWED_FAST_SENT );
if ( pieces != null ){
if ( DEBUG_FAST ){
System.out.println( "Clearing down fast sent for " + getIp());
}
setUserData( KEY_ALLOWED_FAST_SENT, null );
}
}
}finally{
general_mon.exit();
}
}
private void registerForMessageHandling() {
//INCOMING MESSAGES
connection.getIncomingMessageQueue().registerQueueListener(
new IncomingMessageQueue.MessageQueueListener()
{
public final boolean
messageReceived(
Message message )
{
if (Logger.isEnabled())
Logger.log(new LogEvent(PEPeerTransportProtocol.this, LogIDs.NET,
"Received [" + message.getDescription() + "] message"));
final long now =SystemTime.getCurrentTime();
last_message_received_time =now;
if( message.getType() == Message.TYPE_DATA_PAYLOAD ) {
last_data_message_received_time =now;
}
String message_id = message.getID();
if( message_id.equals( BTMessage.ID_BT_PIECE ) ) {
decodePiece( (BTPiece)message );
return true;
}
if( closing ) {
message.destroy();
return true;
}
if(message_id.equals( BTMessage.ID_BT_KEEP_ALIVE ) ) {
message.destroy();
//make sure they're not spamming us
if( !message_limiter.countIncomingMessage( message.getID(), 6, 60*1000 ) ) { //allow max 6 keep-alives per 60sec
System.out.println( manager.getDisplayName() + ": Incoming keep-alive message flood detected, dropping spamming peer connection." +PEPeerTransportProtocol.this );
closeConnectionInternally( "Incoming keep-alive message flood detected, dropping spamming peer connection." );
}
return true;
}
if( message_id.equals( BTMessage.ID_BT_HANDSHAKE ) ) {
decodeBTHandshake( (BTHandshake)message );
return true;
}
if( message_id.equals( AZMessage.ID_AZ_HANDSHAKE ) ) {
decodeAZHandshake( (AZHandshake)message );
return true;
}
if (message_id.equals(LTMessage.ID_LT_HANDSHAKE)) {
decodeLTHandshake((LTHandshake)message);
return true;
}
if( message_id.equals( BTMessage.ID_BT_BITFIELD ) ) {
decodeBitfield( (BTBitfield)message );
return true;
}
if( message_id.equals( BTMessage.ID_BT_CHOKE ) ) {
decodeChoke( (BTChoke)message );
if( choking_other_peer ) {
connection.enableEnhancedMessageProcessing( false, manager.getPartitionID()); //downgrade back to normal handler
}
return true;
}
if( message_id.equals( BTMessage.ID_BT_UNCHOKE ) ) {
decodeUnchoke( (BTUnchoke)message );
connection.enableEnhancedMessageProcessing( true, manager.getPartitionID() ); //make sure we use a fast handler for the resulting download
return true;
}
if( message_id.equals( BTMessage.ID_BT_INTERESTED ) ) {
decodeInterested( (BTInterested)message );
return true;
}
if( message_id.equals( BTMessage.ID_BT_UNINTERESTED ) ) {
decodeUninterested( (BTUninterested)message );
return true;
}
if( message_id.equals( BTMessage.ID_BT_HAVE ) ) {
decodeHave( (BTHave)message );
return true;
}
if( message_id.equals( BTMessage.ID_BT_REQUEST ) ) {
decodeRequest( (BTRequest)message );
return true;
}
if( message_id.equals( BTMessage.ID_BT_CANCEL ) ) {
decodeCancel( (BTCancel)message );
return true;
}
if( message_id.equals( BTMessage.ID_BT_SUGGEST_PIECE ) ) {
decodeSuggestPiece( (BTSuggestPiece)message );
return true;
}
if( message_id.equals( BTMessage.ID_BT_HAVE_ALL ) ) {
decodeHaveAll( (BTHaveAll)message );
return true;
}
if( message_id.equals( BTMessage.ID_BT_HAVE_NONE ) ) {
decodeHaveNone( (BTHaveNone)message );
return true;
}
if( message_id.equals( BTMessage.ID_BT_REJECT_REQUEST ) ) {
decodeRejectRequest( (BTRejectRequest)message );
return true;
}
if( message_id.equals( BTMessage.ID_BT_ALLOWED_FAST ) ) {
decodeAllowedFast( (BTAllowedFast)message );
return true;
}
if (message_id.equals(BTMessage.ID_BT_DHT_PORT)) {
decodeMainlineDHTPort((BTDHTPort)message);
return true;
}
if( message_id.equals( AZMessage.ID_AZ_PEER_EXCHANGE ) ) {
decodePeerExchange( (AZPeerExchange)message );
return true;
}
if (message_id.equals(LTMessage.ID_UT_PEX)) {
decodePeerExchange((UTPeerExchange)message);
return true;
}
if( message_id.equals( AZMessage.ID_AZ_REQUEST_HINT ) ) {
decodeAZRequestHint( (AZRequestHint)message );
return true;
}
if( message_id.equals( AZMessage.ID_AZ_HAVE ) ) {
decodeAZHave((AZHave)message );
return true;
}
if( message_id.equals( AZMessage.ID_AZ_BAD_PIECE ) ) {
decodeAZBadPiece((AZBadPiece)message );
return true;
}
if( message_id.equals( AZMessage.ID_AZ_STAT_REQUEST ) ) {
decodeAZStatsRequest((AZStatRequest)message );
return true;
}
if( message_id.equals( AZMessage.ID_AZ_STAT_REPLY ) ) {
decodeAZStatsReply((AZStatReply)message );
return true;
}
return false;
}
public final void protocolBytesReceived( int byte_count ) {
//update stats
peer_stats.protocolBytesReceived( byte_count );
manager.protocolBytesReceived( PEPeerTransportProtocol.this, byte_count );
}
public final void dataBytesReceived( int byte_count ) {
// Observe that the peer is sending data so that if theyre so slow that the whole
// data block times out, we don't think theyre not sending anything at all
last_data_message_received_time =SystemTime.getCurrentTime();
//update stats
peer_stats.dataBytesReceived( byte_count );
manager.dataBytesReceived( PEPeerTransportProtocol.this, byte_count );
}
public boolean
isPriority()
{
return( false );
}
});
//OUTGOING MESSAGES
connection.getOutgoingMessageQueue().registerQueueListener( new OutgoingMessageQueue.MessageQueueListener() {
public final boolean messageAdded( Message message ) { return true; }
public final void messageQueued( Message message ) { /* ignore */ }
public final void messageRemoved( Message message ) { /*ignore*/ }
public final void messageSent( Message message ) {
//update keep-alive info
final long now =SystemTime.getCurrentTime();
last_message_sent_time =now;
if( message.getType() == Message.TYPE_DATA_PAYLOAD ) {
last_data_message_sent_time =now;
}
if( message.getID().equals( BTMessage.ID_BT_UNCHOKE ) ) { // is about to send piece data
connection.enableEnhancedMessageProcessing( true, manager.getPartitionID() ); //so make sure we use a fast handler
}
else if( message.getID().equals( BTMessage.ID_BT_CHOKE ) ) { // is done sending piece data
if( effectively_choked_by_other_peer ) {
connection.enableEnhancedMessageProcessing( false, manager.getPartitionID() ); //so downgrade back to normal handler
}
}
if (Logger.isEnabled())
Logger.log(new LogEvent(PEPeerTransportProtocol.this, LogIDs.NET,
"Sent [" + message.getDescription() + "] message"));
}
public final void protocolBytesSent( int byte_count ) {
//update stats
peer_stats.protocolBytesSent( byte_count );
manager.protocolBytesSent( PEPeerTransportProtocol.this, byte_count );
}
public final void dataBytesSent( int byte_count ) {
//update stats
peer_stats.dataBytesSent( byte_count );
manager.dataBytesSent( PEPeerTransportProtocol.this, byte_count );
}
public void flush(){}
});
//start message processing
connection.addRateLimiter( manager.getUploadLimitedRateGroup(), true );
connection.addRateLimiter( manager.getDownloadLimitedRateGroup(), false );
connection.startMessageProcessing();
}
public void
addRateLimiter(
LimitedRateGroup limiter,
boolean upload )
{
connection.addRateLimiter( limiter, upload );
}
public void
removeRateLimiter(
LimitedRateGroup limiter,
boolean upload )
{
connection.removeRateLimiter( limiter, upload );
}
public Connection getPluginConnection() {
return plugin_connection;
}
public Message[] getSupportedMessages() {
return supported_messages;
}
public boolean supportsMessaging() {
return supported_messages != null;
}
public int getMessagingMode()
{
return messaging_mode;
}
public byte[] getHandshakeReservedBytes() {
return this.handshake_reserved_bytes;
}
public void
setHaveAggregationEnabled(
boolean enabled )
{
have_aggregation_disabled = !enabled;
}
public boolean
hasReceivedBitField()
{
return( received_bitfield );
}
public long
getUnchokedForMillis()
{
long time = effectively_unchoked_time;
if ( effectively_choked_by_other_peer || time < 0 ){
return( -1 );
}
return( SystemTime.getMonotonousTime() - time );
}
public String
getEncryption()
{
Transport transport = connection.getTransport();
if ( transport == null ){
return( "" );
}
return( transport.getEncryption( false ));
}
public String
getProtocol()
{
Transport transport = connection.getTransport();
if ( transport == null ){
return( "" );
}
return( transport.getProtocol());
}
public void
addListener(
PEPeerListener listener )
{
try{
peer_listeners_mon.enter();
if( peer_listeners_cow == null ){
peer_listeners_cow = new ArrayList();
}
final List new_listeners = new ArrayList( peer_listeners_cow );
new_listeners.add( listener );
peer_listeners_cow = new_listeners;
}finally{
peer_listeners_mon.exit();
}
}
public void
removeListener(
PEPeerListener listener )
{
try{
peer_listeners_mon.enter();
if ( peer_listeners_cow != null ){
List new_listeners = new ArrayList( peer_listeners_cow );
new_listeners.remove( listener );
if ( new_listeners.isEmpty()){
new_listeners = null;
}
peer_listeners_cow = new_listeners;
}
}finally{
peer_listeners_mon.exit();
}
}
private void changePeerState( int new_state ) {
current_peer_state = new_state;
if( current_peer_state == PEPeer.TRANSFERING ) { //YUCK!
doPostHandshakeProcessing();
}
final List peer_listeners_ref = peer_listeners_cow;
if ( peer_listeners_ref != null ){
for( int i=0; i < peer_listeners_ref.size(); i++ ) {
final PEPeerListener l = (PEPeerListener)peer_listeners_ref.get( i );
l.stateChanged(this, current_peer_state);
}
}
}
/**
* this method might be called repeatedly since LTEP allows headers to be sent more than once
* make sure that multiple invokations do not have unintended consequences
*/
private void doPostHandshakeProcessing() {
//peer exchange registration
if( manager.isPeerExchangeEnabled()) {
//try and register all connections for their peer exchange info
PeerExchangerItem pex_item = peer_exchange_item;
if(pex_item == null && canBePeerExchanged()){
pex_item = peer_exchange_item = manager.createPeerExchangeConnection( this );
}
if( pex_item != null ) {
//check for peer exchange support
if(ut_pex_enabled || peerSupportsMessageType(AZMessage.ID_AZ_PEER_EXCHANGE)) {
peer_exchange_supported = true;
pex_item.enableStateMaintenance();
}
else { //no need to maintain internal states as we wont be sending/receiving peer exchange messages
pex_item.disableStateMaintenance();
}
}
}
request_hint_supported = peerSupportsMessageType( AZMessage.ID_AZ_REQUEST_HINT );
bad_piece_supported = peerSupportsMessageType( AZMessage.ID_AZ_BAD_PIECE );
stats_request_supported = peerSupportsMessageType( AZMessage.ID_AZ_STAT_REQUEST );
stats_reply_supported = peerSupportsMessageType( AZMessage.ID_AZ_STAT_REPLY );
}
private boolean
canBePeerExchanged()
{
if ( client_peer_id != null ){
// disable the exchange of location targetted peers
boolean ok = !client_peer_id.startsWith( PeerClassifier.CACHE_LOGIC );
// System.out.println( "canPEX: " + client_peer_id + " -> " + ok );
return( ok );
}else{
Debug.out( "No client peer id!" );
return( false );
}
}
private boolean peerSupportsMessageType( String message_id ) {
if( supported_messages != null ) {
for( int i=0; i < supported_messages.length; i++ ) {
if( supported_messages[i].getID().equals( message_id ) ) return true;
}
}
return false;
}
public void updatePeerExchange() {
if ( current_peer_state != TRANSFERING ) return;
if( !peer_exchange_supported ) return;
PeerExchangerItem pex_item = peer_exchange_item;
if( pex_item != null && manager.isPeerExchangeEnabled()) {
final PeerItem[] adds = pex_item.getNewlyAddedPeerConnections();
final PeerItem[] drops = pex_item.getNewlyDroppedPeerConnections();
if( (adds != null && adds.length > 0) || (drops != null && drops.length > 0) ) {
if (ut_pex_enabled) {
connection.getOutgoingMessageQueue().addMessage( new UTPeerExchange(adds, drops, null, (byte)0), false);
}
else {
connection.getOutgoingMessageQueue().addMessage( new AZPeerExchange( manager.getHash(), adds, drops, other_peer_pex_version ), false );
}
}
}
}
protected void decodePeerExchange( AZStylePeerExchange exchange ) {
// if we're seeding ignore µT-PEXed seeds, Az won't send them in the first place
PeerItem[] added = exchange instanceof UTPeerExchange ? ((UTPeerExchange)exchange).getAddedPeers(!( manager.isSeeding() || Constants.DOWNLOAD_SOURCES_PRETEND_COMPLETE )) : exchange.getAddedPeers();
PeerItem[] dropped = exchange.getDroppedPeers();
int max_added = exchange.getMaxAllowedPeersPerVolley(!has_received_initial_pex, true);
int max_dropped = exchange.getMaxAllowedPeersPerVolley(!has_received_initial_pex, false);
exchange.destroy();
//make sure they're not spamming us
if( !message_limiter.countIncomingMessage( exchange.getID(), 7, 120*1000 ) ) { //allow max 7 PEX per 2min //TODO reduce max after 2308 release?
System.out.println( manager.getDisplayName() + ": Incoming PEX message flood detected, dropping spamming peer connection." +PEPeerTransportProtocol.this );
closeConnectionInternally( "Incoming PEX message flood detected, dropping spamming peer connection." );
return;
}
if( ( added != null && added.length > max_added ) ||
( dropped != null && dropped.length > max_dropped )) {
// log these too-large messages and ignore them - if the swarm really is this large then
// we'll discover the peers soon anyway
if (Logger.isEnabled()){
Logger.log(
new LogEvent(this, LOGID,
"Invalid PEX message received: too large, ignoring this exchange. (added=" + (added==null?0:added.length) + ",dropped=" + (dropped==null?0:dropped.length) +")" ));
}
added = null;
dropped = null;
}
has_received_initial_pex = true;
PeerExchangerItem pex_item = peer_exchange_item;
if( peer_exchange_supported && pex_item != null && manager.isPeerExchangeEnabled()){
if( added != null ) {
for( int i=0; i < added.length; i++ ) {
PeerItem pi = added[i];
manager.peerDiscovered( this, pi );
pex_item.addConnectedPeer( pi );
}
}
if( dropped != null ) {
for( int i=0; i < dropped.length; i++ ) {
pex_item.dropConnectedPeer( dropped[i] );
}
}
}
else {
if (Logger.isEnabled())
Logger.log(new LogEvent(this, LOGID,
"Peer Exchange disabled for this download, "
+ "dropping received exchange message"));
}
}
public boolean
sendRequestHint(
int piece_number,
int offset,
int length,
int life )
{
if ( request_hint_supported ){
AZRequestHint rh = new AZRequestHint( piece_number, offset, length, life, other_peer_az_request_hint_version );
connection.getOutgoingMessageQueue().addMessage( rh, false );
return( true );
}else{
return( false );
}
}
protected void
decodeSuggestPiece(
BTSuggestPiece hint )
{
int piece_number = hint.getPieceNumber();
int offset = 0;
int length = manager.getPieceLength( piece_number );
int life = REQUEST_HINT_MAX_LIFE;
hint.destroy();
if ( life > REQUEST_HINT_MAX_LIFE ){
life = REQUEST_HINT_MAX_LIFE;
}
if ( manager.validateHintRequest( this, piece_number, offset, length )){
if ( request_hint == null ){
// we ignore life time currently as once hinted we don't accept another hint
// until that one is satisfied. This is to prevent too many pieces starting
request_hint = new int[]{ piece_number, offset, length };
}
}
}
protected void
decodeAZRequestHint(
AZRequestHint hint )
{
int piece_number = hint.getPieceNumber();
int offset = hint.getOffset();
int length = hint.getLength();
int life = hint.getLife();
hint.destroy();
if ( life > REQUEST_HINT_MAX_LIFE ){
life = REQUEST_HINT_MAX_LIFE;
}
if ( manager.validateHintRequest( this, piece_number, offset, length )){
if ( request_hint == null ){
// we ignore life time currently as once hinted we don't accept another hint
// until that one is satisfied. This is to prevent too many pieces starting
request_hint = new int[]{ piece_number, offset, length };
}
}
}
public int[]
getRequestHint()
{
return( request_hint );
}
public void
clearRequestHint()
{
request_hint = null;
}
public PeerItem getPeerItemIdentity() { return peer_item_identity; }
public int[]
getReservedPieceNumbers()
{
return( reserved_pieces );
}
public void
addReservedPieceNumber(int piece_number)
{
int[] existing = reserved_pieces;
if ( existing == null ){
reserved_pieces = new int[]{ piece_number };
}else{
int[] updated = new int[existing.length+1];
System.arraycopy( existing, 0, updated, 0, existing.length );
updated[existing.length] = piece_number;
reserved_pieces = updated;
}
}
public void
removeReservedPieceNumber(int piece_number)
{
int[] existing = reserved_pieces;
if ( existing != null ){
if ( existing.length == 1 ){
if ( existing[0] == piece_number ){
reserved_pieces = null;
}
}else{
int[] updated = new int[existing.length-1];
int pos = 0;
boolean found = false;
for (int i=0;i<existing.length;i++){
int pn = existing[i];
if ( found || pn != piece_number ){
if ( pos == updated.length ){
return;
}
updated[pos++] = pn;
}else{
found = true;
}
}
reserved_pieces = updated;
}
}
}
public int
getIncomingRequestCount()
{
if ( outgoing_piece_message_handler == null ){
return( 0 );
}
return outgoing_piece_message_handler.getRequestCount();
}
public int
getOutgoingRequestCount()
{
return( getNbRequests());
}
public int
getOutboundDataQueueSize()
{
return( connection.getOutgoingMessageQueue().getTotalSize());
}
public boolean
isStalledPendingLoad()
{
if ( outgoing_piece_message_handler == null ){
return( false );
}
return outgoing_piece_message_handler.isStalledPendingLoad();
}
public int[] getIncomingRequestedPieceNumbers() {
if ( outgoing_piece_message_handler == null ){
return( new int[0]);
}
return outgoing_piece_message_handler.getRequestedPieceNumbers();
}
public int[] getOutgoingRequestedPieceNumbers() {
try{
requested_mon.enter();
/** Cheap hack to reduce (but not remove all) the # of duplicate entries */
int iLastNumber = -1;
// allocate max size needed (we'll shrink it later)
final int[] pieceNumbers = new int[requested.size()];
int pos = 0;
for (int i = 0; i < requested.size(); i++) {
DiskManagerReadRequest request = null;
try {
request = (DiskManagerReadRequest) requested.get(i);
} catch (Exception e) {
Debug.printStackTrace(e);
}
if (request != null && iLastNumber != request.getPieceNumber()) {
iLastNumber = request.getPieceNumber();
pieceNumbers[pos++] = iLastNumber;
}
}
final int[] trimmed = new int[pos];
System.arraycopy(pieceNumbers, 0, trimmed, 0, pos);
return trimmed;
}finally{
requested_mon.exit();
}
}
public int
getPercentDoneOfCurrentIncomingRequest()
{
return( connection.getIncomingMessageQueue().getPercentDoneOfCurrentMessage());
}
public int
getPercentDoneOfCurrentOutgoingRequest()
{
return( connection.getOutgoingMessageQueue().getPercentDoneOfCurrentMessage());
}
/* (non-Javadoc)
* @see org.gudy.azureus2.core3.logging.LogRelation#getLogRelationText()
*/
public String getRelationText() {
String text = "";
if (manager instanceof LogRelation)
text = ((LogRelation)manager).getRelationText() + "; ";
text += "Peer: " + toString();
return text;
}
/* (non-Javadoc)
* @see org.gudy.azureus2.core3.logging.LogRelation#queryForClass(java.lang.Class)
*/
public Object[] getQueryableInterfaces() {
return new Object[] { manager };
}
public int getLastPiece()
{
return _lastPiece;
}
public void setLastPiece(int pieceNumber)
{
_lastPiece =pieceNumber;
}
public boolean isLANLocal() {
if( connection == null ) return( AddressUtils.isLANLocalAddress( ip ) == AddressUtils.LAN_LOCAL_YES );
return connection.isLANLocal();
}
public boolean
isTCP()
{
return( connection.getEndpoint().getProtocols()[0].getType() == ProtocolEndpoint.PROTOCOL_TCP );
}
public void setUploadRateLimitBytesPerSecond( int bytes ){ connection.setUploadLimit( bytes ); }
public void setDownloadRateLimitBytesPerSecond( int bytes ){ connection.setDownloadLimit( bytes ); }
public int getUploadRateLimitBytesPerSecond(){ return connection.getUploadLimit(); }
public int getDownloadRateLimitBytesPerSecond(){ return connection.getDownloadLimit(); }
public String getClientNameFromPeerID() {return this.client_peer_id;}
public String getClientNameFromExtensionHandshake() {
if (!this.client_handshake.equals("") && !this.client_handshake_version.equals("")) {
return this.client_handshake + " " + this.client_handshake_version;
}
return this.client_handshake;
}
private static MainlineDHTProvider getDHTProvider() {
return AzureusCoreImpl.getSingleton().getGlobalManager().getMainlineDHTProvider();
}
public void
setPriorityConnection(
boolean is_priority )
{
synchronized( this ){
if ( priority_connection == is_priority ){
return;
}
priority_connection = is_priority;
}
manager.getAdapter().priorityConnectionChanged( is_priority );
}
public boolean
isPriorityConnection()
{
synchronized( this ){
return( priority_connection );
}
}
protected static List<Integer>
generateFastSet(
byte[] hash,
String ip,
int num_pieces,
int num_required )
{
List<Integer> res = new ArrayList<Integer>();
try{
byte[] address = InetAddress.getByName( ip ).getAddress();
// no IPv6 support yet
if ( address.length == 4 ){
byte[] bytes = new byte[24];
System.arraycopy( address, 0, bytes, 0, 3 );
System.arraycopy( hash, 0, bytes, 4, 20 );
num_required = Math.min( num_required, num_pieces );
while( res.size() < num_required ){
bytes = new SHA1Simple().calculateHash( bytes );
int pos = 0;
while( pos < 20 && res.size() < num_required ){
long index = (bytes[pos++] << 24 )&0xff000000L |
(bytes[pos++] << 16 )&0x00ff0000L |
(bytes[pos++] << 8 )&0x0000ff00L |
bytes[pos++]&0x000000ffL;
Integer i = new Integer((int)( index%num_pieces ));
if ( !res.contains(i)){
res.add( i );
}
}
}
}
}catch( Throwable e ){
Debug.out( "Fast set generation failed", e );
}
return( res );
}
protected List<Integer>
generateFastSet(
int num )
{
return( generateFastSet( manager.getHash(), getIp(), nbPieces, num ));
}
public void
generateEvidence(
IndentWriter writer )
{
writer.println(
"ip=" + getIp() + ",in=" + isIncoming() + ",port=" + getPort() + ",cli=" + client + ",tcp=" + getTCPListenPort() + ",udp=" + getUDPListenPort() +
",oudp=" + getUDPNonDataListenPort() + ",p_state=" + getPeerState() + ",c_state=" + getConnectionState() + ",seed=" + isSeed() + "partialSeed=" + isRelativeSeed() + ",pex=" + peer_exchange_supported + ",closing=" + closing );
writer.println( " choked=" + effectively_choked_by_other_peer + "/" + really_choked_by_other_peer + ",choking=" + choking_other_peer + ",is_opt=" + is_optimistic_unchoke );
writer.println( " interested=" + interested_in_other_peer + ",interesting=" + other_peer_interested_in_me + ",snubbed=" + snubbed );
writer.println( " lp=" + _lastPiece + ",up=" + uniquePiece + ",rp=" + reserved_pieces );
writer.println(
" last_sent=" + last_message_sent_time + "/" + last_data_message_sent_time +
",last_recv=" + last_message_received_time + "/" + last_data_message_received_time + "/" + last_good_data_time );
writer.println( " conn_at=" + connection_established_time + ",cons_no_reqs=" + consecutive_no_request_count +
",discard=" + requests_discarded + "/" + requests_discarded_endgame + ",recov=" + requests_recovered + ",comp=" + requests_completed );
}
protected static class
MutableInteger
{
private int value;
protected
MutableInteger(
int v )
{
value = v;
}
protected void
setValue(
int v )
{
value = v;
}
protected int
getValue()
{
return( value );
}
public int
hashCode()
{
return value;
}
public boolean
equals(
Object obj )
{
if (obj instanceof MutableInteger) {
return value == ((MutableInteger)obj).value;
}
return false;
}
}
public static void
main(
String[] args )
{
byte[] hash = new byte[20];
Arrays.fill( hash, (byte)0xAA );
try{
List<Integer> res = generateFastSet( hash, "80.4.4.200", 9, 5 );
System.out.println( res );
}catch( Throwable e ){
e.printStackTrace();
}
}
}