tracker_interval = time_to_wait = ((Long) metaData.get("interval")).longValue();
Long raw_min_interval = (Long) metaData.get("min interval");
if (Logger.isEnabled()) {
Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_INFORMATION,
"Received from announce: 'interval' = " + time_to_wait
+ "; 'min interval' = " + raw_min_interval));
}
// guard against crazy return values
if (time_to_wait < 0 || time_to_wait > 0xffffffffL) {
time_to_wait = 0xffffffffL;
}
if (raw_min_interval != null) {
tracker_min_interval = min_interval = raw_min_interval.longValue();
// ignore useless values
// Note: Many trackers set min_interval and interval the same.
if (min_interval < 1) {
if (Logger.isEnabled()) {
Logger.log(new LogEvent(
torrent,
LOGID,
LogEvent.LT_INFORMATION,
"Tracker being silly and "
+ "returning a 'min interval' of less than 1 second ("
+ min_interval + ")"));
}
min_interval = 0;
} else if (min_interval > time_to_wait) {
if (Logger.isEnabled()) {
Logger.log(new LogEvent(
torrent,
LOGID,
LogEvent.LT_INFORMATION,
"Tracker being silly and "
+ "returning a 'min interval' ("
+ min_interval
+ ") greater than recommended announce 'interval'"
+ " (" + time_to_wait + ")"));
}
min_interval = 0;
}
} else {
// tracker owners complain we announce too much but then never
// implement "min interval". So take it into our own hands
// and enforce a min_interval of interval when there is no
// "min interval"
min_interval = time_to_wait > 30 ? time_to_wait - 10 : time_to_wait;
}
if(userMinInterval != 0)
{
time_to_wait = Math.max(userMinInterval, time_to_wait);
min_interval = Math.max(min_interval, userMinInterval);
if (Logger.isEnabled()) {
Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_INFORMATION,
"Overriding with user settings: 'interval' = " + time_to_wait
+ "; 'min interval' = " + min_interval));
}
}
// roll back 10 seconds to make sure we announce before the tracker
// times us out. This is done after min_interval in order not to
// mess up the "ignore useless values"
if (time_to_wait > 30)
time_to_wait -= 10;
} catch (Exception e) {
byte[] failure_reason_bytes = (byte[]) metaData.get("failure reason");
if ( failure_reason_bytes == null ){
if (Logger.isEnabled())
Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_WARNING,
"Problems with Tracker, will retry in "
+ getErrorRetryInterval() + "ms"));
return( new TRTrackerAnnouncerResponseImpl( url, torrent_hash, TRTrackerAnnouncerResponse.ST_OFFLINE, getErrorRetryInterval(), "Unknown cause" ));
}
// explicit failure from the tracker
failure_reason = new String( failure_reason_bytes, Constants.DEFAULT_ENCODING);
return( new TRTrackerAnnouncerResponseImpl( url, torrent_hash, TRTrackerAnnouncerResponse.ST_REPORTED_ERROR, getErrorRetryInterval(), failure_reason ));
}
//System.out.println("Response from Announce: " + new String(data));
Long incomplete_l = getLong( metaData, "incomplete");
Long complete_l = getLong( metaData, "complete");
Long downloaded_l = getLong( metaData, "downloaded");
if ( incomplete_l != null || complete_l != null ){
if (Logger.isEnabled())
Logger.log(new LogEvent(torrent, LOGID,
"ANNOUNCE SCRAPE1: seeds=" + complete_l + " peers="
+ incomplete_l));
}
//TrackerID extension, used by phpbt trackers.
//We reply with '&trackerid=1234' when we receive
//'10:tracker id4:1234e' on announce reply.
//NOTE: we receive as 'tracker id' but reply as 'trackerid'
byte[] trackerid = (byte[])metaData.get( "tracker id" );
if( trackerid != null ) {
tracker_id = new String( trackerid );
}
byte[] crypto_flags = (byte[])metaData.get( "crypto_flags" );
//build the list of peers
List valid_meta_peers = new ArrayList();
Object meta_peers_peek = metaData.get( "peers" );
Long az_compact_l = (Long)metaData.get( "azcompact" );
long az_compact = az_compact_l==null?0:az_compact_l.longValue();
boolean this_is_az_tracker = az_compact == 2;
if ( az_tracker != this_is_az_tracker || lastUsedUrl != lastAZTrackerCheckedURL ){
lastAZTrackerCheckedURL = lastUsedUrl;
az_tracker = this_is_az_tracker;
TRTrackerUtils.setAZTracker( url, az_tracker );
}
if ( az_compact == 2 ){
// latest return to dictionary based data
List meta_peers = (List)meta_peers_peek;
int peers_length = meta_peers.size();
if (Logger.isEnabled()) {
Logger.log(new LogEvent(torrent, LOGID,
"ANNOUNCE CompactPeers2: num=" + peers_length));
}
if ( peers_length > 1 ){
// calculate average rtt to use for those with no rtt
long total_rtt = 0;
int rtt_count = 0;
for ( int i = 0; i < peers_length; i++ ){
Map peer = (Map) meta_peers.get(i);
Long l_rtt = (Long)peer.get( "r" );
if ( l_rtt != null ){
long rtt = l_rtt.longValue();
if ( rtt <= 0 ){
// invalid, remove
peer.remove( "r" );
}else{
total_rtt += rtt;
}
rtt_count++;
}
}
final int average_rtt = (int)( rtt_count==0?0:(total_rtt/rtt_count));
// sort into smallest rtt order with biased at front
Collections.sort(
meta_peers,
new Comparator()
{
public int
compare(
Object o1,
Object o2 )
{
Map map1 = (Map)o1;
Map map2 = (Map)o2;
Long l_rtt1 = (Long)map1.get( "r" );
Long l_rtt2 = (Long)map2.get( "r" );
boolean biased_1 = map1.containsKey( "b" );
boolean biased_2 = map2.containsKey( "b" );
if ( biased_1 == biased_2 ){
int rtt1 = l_rtt1==null?average_rtt:l_rtt1.intValue();
int rtt2 = l_rtt2==null?average_rtt:l_rtt2.intValue();
return( rtt1 - rtt2 );
}else if ( biased_1 ){
return( -1 );
}else{
return( +1 );
}
}
});
// interleave non-biased peers with good rtt
int biased_pos = peers_length;
int non_biased_pos = peers_length;
for ( int i = 0; i < peers_length; i++ ){
Map peer = (Map) meta_peers.get(i);
if ( peer.containsKey( "b" )){
if ( i == 0 ){
biased_pos = i;
}
}else{
non_biased_pos = i;
break;
}
}
List new_peers = new ArrayList(peers_length);
int non_biased_start = non_biased_pos;
boolean last_biased = true;
while( biased_pos < non_biased_start || non_biased_pos < peers_length ){
if ( biased_pos < non_biased_start ){
if ( non_biased_pos < peers_length ){
Map biased = (Map) meta_peers.get(biased_pos);
Map non_biased = (Map) meta_peers.get(non_biased_pos);
boolean use_biased;
if ( !last_biased ){
use_biased = true;
}else{
Long l_rtt_biased = (Long)biased.get( "r" );
Long l_rtt_non_biased = (Long)non_biased.get( "r" );
int biased_rtt = l_rtt_biased==null?average_rtt:l_rtt_biased.intValue();
int non_biased_rtt = l_rtt_non_biased==null?average_rtt:l_rtt_non_biased.intValue();
use_biased = non_biased_rtt >= biased_rtt;
}
if ( use_biased ){
new_peers.add( biased );
biased_pos++;
}else{
new_peers.add( non_biased );
non_biased_pos++;
}
last_biased = use_biased;
}else{
new_peers.add( meta_peers.get( biased_pos++ ));
}
}else{
new_peers.add( meta_peers.get( non_biased_pos++ ));
}
}
meta_peers = new_peers;
}
for ( int i = 0; i < peers_length; i++ ){
Map peer = (Map) meta_peers.get(i);
try{
byte[] ip_bytes = (byte[])peer.get("i");
String ip;
if ( ip_bytes.length == 4 ){
int ip1 = 0xff & ip_bytes[0];
int ip2 = 0xff & ip_bytes[1];
int ip3 = 0xff & ip_bytes[2];
int ip4 = 0xff & ip_bytes[3];
ip = ip1 + "." + ip2 + "." + ip3 + "." + ip4;
}else{
StringBuffer sb = new StringBuffer(39);
for ( int j=0; j<16; j+=2 ){
sb.append(
Integer.toHexString(((ip_bytes[j]<<8) & 0xff00) | (ip_bytes[j+1]&0x00ff)));
if (j < 14 ){
sb.append( ":" );
}
}
ip = sb.toString();
}
byte[] tcp_bytes = (byte[])peer.get("t");
int tcp_port = ((tcp_bytes[0]&0xff) << 8 ) + (tcp_bytes[1]&0xff );
byte[] peer_peer_id = TRTrackerAnnouncerImpl.getAnonymousPeerId( ip, tcp_port );
int udp_port = 0;
byte[] udp_bytes = (byte[])peer.get("u");
if ( udp_bytes != null ){
if ( udp_bytes.length == 0 ){
udp_port = tcp_port;
}else{
udp_port = ((udp_bytes[0]&0xff) << 8 ) + (udp_bytes[1]&0xff );
}
}
int http_port = 0;
byte[] http_bytes = (byte[])peer.get("h");
if ( http_bytes != null ){
http_port = ((http_bytes[0]&0xff) << 8 ) + (http_bytes[1]&0xff );
}
short protocol = DownloadAnnounceResultPeer.PROTOCOL_NORMAL;
byte[] protocol_bytes = (byte[])peer.get("c");
if ( protocol_bytes != null ){
protocol = (protocol_bytes[0]&0x01)==0?DownloadAnnounceResultPeer.PROTOCOL_NORMAL:DownloadAnnounceResultPeer.PROTOCOL_CRYPT;
}
Long l_azver = (Long)peer.get("v" );
byte az_ver = l_azver==null?TRTrackerAnnouncer.AZ_TRACKER_VERSION_1:l_azver.byteValue();
Long l_up_speed = (Long)peer.get( "s" );
boolean biased = peer.containsKey("b");
if ( biased ){
PeerClassifier.setAzureusIP( ip );
}
TRTrackerAnnouncerResponsePeerImpl new_peer =
new TRTrackerAnnouncerResponsePeerImpl(
PEPeerSource.PS_BT_TRACKER,
peer_peer_id,
ip,
tcp_port,
udp_port,
http_port,
protocol,
az_ver,
l_up_speed==null?0:l_up_speed.shortValue());
if (Logger.isEnabled()){
String extra = "";
Long l_rtt = (Long)peer.get( "r" );
if ( l_rtt != null ){
extra = ",rtt=" + l_rtt;
}
if ( biased ){
extra += ",biased";
}
Logger.log(new LogEvent(torrent, LOGID,
"AZ2-COMPACT PEER: " + new_peer.getString() + extra));
}
valid_meta_peers.add( new_peer );
}catch( Throwable e ){
if (Logger.isEnabled())
Logger.log(
new LogEvent(
torrent, LOGID, LogEvent.LT_ERROR,
"Invalid az2 peer received: " + peer ));
}
}
}else if ( meta_peers_peek instanceof List ){
// old style non-compact
List meta_peers = (List)meta_peers_peek;
//for every peer
int peers_length = meta_peers.size();
if (Logger.isEnabled()) {
Logger.log(new LogEvent(torrent, LOGID,
"ANNOUNCE old style non-compact: num=" + peers_length));
}
if ( crypto_flags != null && peers_length != crypto_flags.length ){
crypto_flags = null;
if (Logger.isEnabled())
Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
"Invalid crypto_flags returned: length mismatch" ));
}
for (int i = 0; i < peers_length; i++) {
Map peer = (Map) meta_peers.get(i);
Object s_peerid = peer.get("peer id");
Object s_ip = peer.get("ip");
Object s_port = peer.get("port");
// Assert that all ip and port are available
if ( s_ip != null && s_port != null ){
//get the peer ip address
String ip = new String((byte[]) s_ip, Constants.DEFAULT_ENCODING);
//get the peer port number - should be Long but have seen byte[] on occasion
int peer_port = s_port instanceof byte[]?Integer.parseInt(new String((byte[])s_port)):((Long) s_port).intValue();
// try to repair invalid peer ports; worst that can happen is we
// still can't make outgoing connections that we already can't make
if (peer_port >65535)
peer_port -=65536;
if (peer_port <0)
peer_port +=65536;
if (peer_port < 0 || peer_port > 65535) {
if (Logger.isEnabled())
Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
"Invalid peer port given: " + ip + ": " + peer_port));
continue;
}
byte[] peer_peer_id;
// extension - if peer id is missing then the tracker isn't sending
// peer ids to save on bandwidth. However, we need something "unique" to
// work on internally so make an ID up from the ip and port
if ( s_peerid == null ){
// Debug.out(ip + ": tracker did not give peerID in reply");
peer_peer_id = TRTrackerAnnouncerImpl.getAnonymousPeerId( ip, peer_port );
// System.out.println("generated peer id" + new String(peerId) + "/" + ByteFormatter.nicePrint( peerId, true ));
}else{
peer_peer_id = (byte[])s_peerid ;
}
short protocol;
if ( crypto_flags == null ){
protocol = DownloadAnnounceResultPeer.PROTOCOL_NORMAL;
}else{
protocol = crypto_flags[i]==0?DownloadAnnounceResultPeer.PROTOCOL_NORMAL:DownloadAnnounceResultPeer.PROTOCOL_CRYPT;
}
int udp_port = 0;
int http_port = 0;
TRTrackerAnnouncerResponsePeerImpl new_peer =
new TRTrackerAnnouncerResponsePeerImpl(
PEPeerSource.PS_BT_TRACKER,
peer_peer_id,
ip,
peer_port,
udp_port,
http_port,
protocol,
TRTrackerAnnouncer.AZ_TRACKER_VERSION_1,
(short)0 );
if (Logger.isEnabled())
Logger.log(new LogEvent(torrent, LOGID,
"NON-COMPACT PEER: " + new_peer.getString()));
valid_meta_peers.add( new_peer );
}
}
}else if ( meta_peers_peek instanceof byte[] ){
// byte[] for compact returns
byte[] meta_peers = (byte[])meta_peers_peek;
int entry_size = az_compact==1?9:6;
if ( crypto_flags != null && meta_peers.length/entry_size != crypto_flags.length ){
crypto_flags = null;
if (Logger.isEnabled())
Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
"Invalid crypto_flags returned: length mismatch" ));
}
int peer_number = 0;
if (Logger.isEnabled()) {
Logger.log(new LogEvent(torrent, LOGID,
"ANNOUNCE CompactPeers: num=" + (meta_peers.length/entry_size)));
}
for (int i=0;i<meta_peers.length;i+=entry_size){
peer_number++;
int ip1 = 0xFF & meta_peers[i];
int ip2 = 0xFF & meta_peers[i+1];
int ip3 = 0xFF & meta_peers[i+2];
int ip4 = 0xFF & meta_peers[i+3];
int po1 = 0xFF & meta_peers[i+4];
int po2 = 0xFF & meta_peers[i+5];
String ip = "" + ip1 + "." + ip2 + "." + ip3 + "." + ip4;
int tcp_port = po1*256+po2;
if (tcp_port < 0 || tcp_port > 65535) {
if (Logger.isEnabled())
Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
"Invalid compact peer port given: " + ip + ": "
+ tcp_port));
continue;
}
byte[] peer_peer_id = TRTrackerAnnouncerImpl.getAnonymousPeerId( ip, tcp_port );
short protocol;
int udp_port;
if ( az_compact == 1 ){
int upo1 = 0xFF & meta_peers[i+6];
int upo2 = 0xFF & meta_peers[i+7];
udp_port = upo1*256+upo2;
byte flags = meta_peers[i+8];
protocol = (flags&0x01)==0?DownloadAnnounceResultPeer.PROTOCOL_NORMAL:DownloadAnnounceResultPeer.PROTOCOL_CRYPT;
}else{
if ( crypto_flags == null ){
protocol = DownloadAnnounceResultPeer.PROTOCOL_NORMAL;
}else{
protocol = crypto_flags[peer_number-1]==0?DownloadAnnounceResultPeer.PROTOCOL_NORMAL:DownloadAnnounceResultPeer.PROTOCOL_CRYPT;
}
udp_port = 0;
}
int http_port = 0;
TRTrackerAnnouncerResponsePeerImpl peer =
new TRTrackerAnnouncerResponsePeerImpl(
PEPeerSource.PS_BT_TRACKER,
peer_peer_id,
ip,
tcp_port,
udp_port,
http_port,
protocol,
TRTrackerAnnouncer.AZ_TRACKER_VERSION_1,
(short)0 );
if (Logger.isEnabled())
Logger.log(new LogEvent(torrent, LOGID, "COMPACT PEER: " + peer.getString()));
valid_meta_peers.add( peer );
}
}else if ( meta_peers_peek instanceof Map ){
// some trackers incorrectly return an empty Map when no peers available
if (((Map)meta_peers_peek).size() != 0 ){
throw( new IOException( "peers missing from response" ));
}
}else if (!metaData.containsKey("peers6")){
// we got nothing useful under peers and no peers6 either
throw( new IOException( "peers missing from response" ));
}
final byte[] v6peers = (byte[])metaData.get("peers6");
if ( v6peers != null ){
// 16 bytes for v6 + 2 bytes for port
final int entry_size = 18;
final byte[] rawAddr = new byte[16];
for (int i=0; i<v6peers.length; i+=entry_size ){
System.arraycopy( v6peers, i, rawAddr, 0, 16 );
String ip = InetAddress.getByAddress(rawAddr).getHostAddress();
int po1 = 0xFF & v6peers[i+16];
int po2 = 0xFF & v6peers[i+17];
int tcp_port = po1*256 + po2;
if (tcp_port < 0 || tcp_port > 65535){
if (Logger.isEnabled()){
Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR, "Invalid compactv6 peer port given: " + ip + ": " + tcp_port));
}
continue;
}
byte[] peer_peer_id = TRTrackerAnnouncerImpl.getAnonymousPeerId( ip, tcp_port );
short protocol = DownloadAnnounceResultPeer.PROTOCOL_NORMAL;
TRTrackerAnnouncerResponsePeerImpl peer = new TRTrackerAnnouncerResponsePeerImpl(PEPeerSource.PS_BT_TRACKER, peer_peer_id, ip, tcp_port, 0, 0, protocol, TRTrackerAnnouncer.AZ_TRACKER_VERSION_1, (short) 0);
if (Logger.isEnabled()){
Logger.log(new LogEvent(torrent, LOGID, "COMPACTv6 PEER: " + peer.getString()));
}
valid_meta_peers.add(peer);
}
}
TRTrackerAnnouncerResponsePeerImpl[] peers=new TRTrackerAnnouncerResponsePeerImpl[valid_meta_peers.size()];
valid_meta_peers.toArray(peers);
helper.addToTrackerCache( peers);
TRTrackerAnnouncerResponseImpl resp = new TRTrackerAnnouncerResponseImpl( url, torrent_hash, TRTrackerAnnouncerResponse.ST_ONLINE, time_to_wait, peers );
//reset failure retry interval on successful connect
failure_added_time = 0;
Map extensions = (Map)metaData.get( "extensions" );
resp.setExtensions(extensions);
if ( extensions != null ){
if ( complete_l == null) {
complete_l = (Long)extensions.get("complete");
}
if ( incomplete_l == null) {
incomplete_l = (Long)extensions.get("incomplete");
}
if (Logger.isEnabled())
Logger.log(new LogEvent(torrent, LOGID,
"ANNOUNCE SCRAPE2: seeds=" + complete_l + " peers="
+ incomplete_l));
Object override = extensions.get( "min interval override" );
if ( override != null && override instanceof Long ){
// this is to allow specific torrents to be refreshed more quickly
// if the tracker permits. Parg
min_interval_override = ((Long)override).longValue();
}
}
if (complete_l != null || incomplete_l != null) {
int complete = complete_l == null ? 0 : complete_l.intValue();
int incomplete = incomplete_l == null ? 0 : incomplete_l.intValue();
if (complete < 0 || incomplete < 0) {
resp.setFailurReason(MessageText.getString(
"Tracker.announce.ignorePeerSeed",
new String[] { (complete < 0
? MessageText.getString("MyTorrentsView.seeds") + " == "
+ complete + ". " : "")
+ (incomplete < 0
? MessageText.getString("MyTorrentsView.peers")
+ " == " + incomplete + ". " : "") }));
} else {
resp.setScrapeResult( complete, incomplete );
TRTrackerScraper scraper = TRTrackerScraperFactory.getSingleton();
if (scraper != null) {
TRTrackerScraperResponse scrapeResponse = scraper.scrape( torrent, getTrackerURL());
if (scrapeResponse != null) {
long lNextScrapeTime = scrapeResponse.getNextScrapeStartTime();
long lNewNextScrapeTime = TRTrackerScraperResponseImpl.calcScrapeIntervalSecs(
0, complete) * 1000;
// make it look as if the scrape has just run. Important
// as seeding rules may make calculations on when the
// scrape value were set
scrapeResponse.setScrapeStartTime(SystemTime.getCurrentTime());
if (lNextScrapeTime < lNewNextScrapeTime)
scrapeResponse.setNextScrapeStartTime(lNewNextScrapeTime);
scrapeResponse.setSeedsPeers(complete, incomplete);
if ( downloaded_l != null ){
scrapeResponse.setCompleted( downloaded_l.intValue());
}
}
}
}
}
return (resp);
}catch( IOException e ){
// decode could fail if the tracker's returned, say, an HTTP response
// indicating server overload
String trace_data;
if ( data.length <= 150 ){
trace_data = new String(data);
}else{
trace_data = new String(data,0,150) + "...";
}
if (Logger.isEnabled())
Logger.log(new LogEvent(torrent, LOGID, LogEvent.LT_ERROR,
"TRTrackerAnnouncer::invalid reply: " + trace_data));
failure_reason = "invalid reply: " + trace_data;
}
}catch( Throwable e ){