/* * Created on 14-Feb-2005 * Created by Paul Gardner * Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package org.gudy.azureus2.core3.tracker.client.impl; import java.io.UnsupportedEncodingException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.internat.MessageText; import org.gudy.azureus2.core3.logging.*; import org.gudy.azureus2.core3.peer.PEPeerSource; import org.gudy.azureus2.core3.torrent.TOTorrent; import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer; import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerException; import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerListener; import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponse; import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponsePeer; import org.gudy.azureus2.core3.util.AEMonitor; import org.gudy.azureus2.core3.util.Constants; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.LightHashMap; import org.gudy.azureus2.core3.util.ListenerManager; import org.gudy.azureus2.core3.util.ListenerManagerDispatcher; import org.gudy.azureus2.core3.util.RandomUtils; import org.gudy.azureus2.plugins.clientid.ClientIDException; import org.gudy.azureus2.plugins.download.DownloadAnnounceResultPeer; import org.gudy.azureus2.pluginsimpl.local.clientid.ClientIDManagerImpl; import com.aelitis.azureus.core.tracker.TrackerPeerSource; import com.aelitis.azureus.core.tracker.TrackerPeerSourceAdapter; /** * @author parg * */ public abstract class TRTrackerAnnouncerImpl implements TRTrackerAnnouncer { // Used to be componentID 2 public final static LogIDs LOGID = LogIDs.TRACKER; // listener protected static final int LDT_TRACKER_RESPONSE = 1; protected static final int LDT_URL_CHANGED = 2; protected static final int LDT_URL_REFRESH = 3; private static final String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; private static final int key_id_length = 8; private static String createKeyID() { String key_id = ""; for (int i = 0; i < key_id_length; i++) { int pos = RandomUtils.nextInt( chars.length()); key_id += chars.charAt(pos); } return( key_id ); } protected ListenerManager<TRTrackerAnnouncerListener> listeners = ListenerManager.createManager( "TrackerClient:ListenDispatcher", new ListenerManagerDispatcher<TRTrackerAnnouncerListener>() { public void dispatch( TRTrackerAnnouncerListener listener, int type, Object value ) { if ( type == LDT_TRACKER_RESPONSE ){ listener.receivedTrackerResponse((TRTrackerAnnouncerResponse)value); }else if ( type == LDT_URL_CHANGED ){ Object[] x = (Object[])value; URL old_url = (URL)x[0]; URL new_url = (URL)x[1]; boolean explicit = ((Boolean)x[2]).booleanValue(); listener.urlChanged( TRTrackerAnnouncerImpl.this, old_url, new_url, explicit ); }else{ listener.urlRefresh(); } } }); private Map tracker_peer_cache = new LinkedHashMap(); // insertion order - most recent at end private AEMonitor tracker_peer_cache_mon = new AEMonitor( "TRTrackerClientClassic:PC" ); private int cache_peers_used; final private TOTorrent torrent; final private byte[] peer_id; final private String tracker_key; final private int udp_key; protected TRTrackerAnnouncerImpl( TOTorrent _torrent ) throws TRTrackerAnnouncerException { torrent = _torrent; tracker_key = createKeyID(); udp_key = RandomUtils.nextInt(); try{ peer_id = ClientIDManagerImpl.getSingleton().generatePeerID( torrent, false ); }catch( ClientIDException e ){ throw( new TRTrackerAnnouncerException( "TRTrackerAnnouncer: Peer ID generation fails", e )); } } public TOTorrent getTorrent() { return( torrent ); } public Helper getHelper() { return( new Helper() { public byte[] getPeerID() { return( peer_id ); } public String getTrackerKey() { return( tracker_key ); } public int getUDPKey() { return( udp_key ); } public void addToTrackerCache( TRTrackerAnnouncerResponsePeerImpl[] peers ) { TRTrackerAnnouncerImpl.this.addToTrackerCache( peers ); } public TRTrackerAnnouncerResponsePeer[] getPeersFromCache( int num_want ) { return( TRTrackerAnnouncerImpl.this.getPeersFromCache(num_want)); } public void setTrackerResponseCache( Map map ) { TRTrackerAnnouncerImpl.this.setTrackerResponseCache( map ); } public void removeFromTrackerResponseCache( String ip, int tcpPort ) { TRTrackerAnnouncerImpl.this.removeFromTrackerResponseCache( ip,tcpPort ); } public Map getTrackerResponseCache() { return( TRTrackerAnnouncerImpl.this.getTrackerResponseCache()); } public void informResponse( TRTrackerAnnouncerHelper helper, TRTrackerAnnouncerResponse response ) { TRTrackerAnnouncerImpl.this.informResponse( helper, response ); } public void informURLChange( URL old_url, URL new_url, boolean explicit ) { listeners.dispatch( LDT_URL_CHANGED, new Object[]{old_url, new_url, new Boolean(explicit)}); } public void informURLRefresh() { TRTrackerAnnouncerImpl.this.informURLRefresh(); } public void addListener( TRTrackerAnnouncerListener l ) { TRTrackerAnnouncerImpl.this.addListener( l ); } public void removeListener( TRTrackerAnnouncerListener l ) { TRTrackerAnnouncerImpl.this.removeListener( l ); } }); } public byte[] getPeerId() { return( peer_id ); } public static byte[] getAnonymousPeerId( String my_ip, int my_port ) { byte[] anon_peer_id = new byte[20]; // unique initial two bytes to identify this as fake anon_peer_id[0] = (byte)'['; anon_peer_id[1] = (byte)']'; try{ byte[] ip_bytes = my_ip.getBytes( Constants.DEFAULT_ENCODING ); int ip_len = ip_bytes.length; if ( ip_len > 18 ){ ip_len = 18; } System.arraycopy( ip_bytes, 0, anon_peer_id, 2, ip_len ); int port_copy = my_port; for (int j=2+ip_len;j<20;j++){ anon_peer_id[j] = (byte)(port_copy&0xff); port_copy >>= 8; } }catch( UnsupportedEncodingException e ){ Debug.printStackTrace( e ); } return( anon_peer_id ); } // NOTE: tracker_cache is cleared out in DownloadManager when opening a torrent for the // first time as a DOS prevention measure public Map getTrackerResponseCache() { return( exportTrackerCache()); } public void setTrackerResponseCache( Map map ) { int num = importTrackerCache( map ); if (Logger.isEnabled()) Logger.log(new LogEvent(getTorrent(), LOGID, "TRTrackerClient: imported " + num + " cached peers")); } protected Map exportTrackerCache() { Map res = new LightHashMap(1); List peers = new ArrayList(); res.put( "tracker_peers", peers ); try{ tracker_peer_cache_mon.enter(); Iterator it = tracker_peer_cache.values().iterator(); while( it.hasNext()){ TRTrackerAnnouncerResponsePeer peer = (TRTrackerAnnouncerResponsePeer)it.next(); LightHashMap entry = new LightHashMap(); entry.put( "ip", peer.getAddress().getBytes()); entry.put( "src", peer.getSource().getBytes()); entry.put( "port", new Long(peer.getPort())); int udp_port = peer.getUDPPort(); if ( udp_port != 0 ){ entry.put( "udpport", new Long( udp_port)); } int http_port = peer.getHTTPPort(); if ( http_port != 0 ){ entry.put( "httpport", new Long( http_port)); } entry.put( "prot", new Long(peer.getProtocol())); byte az_ver = peer.getAZVersion(); if ( az_ver != TRTrackerAnnouncer.AZ_TRACKER_VERSION_1 ){ entry.put( "azver", new Long( az_ver )); } entry.compactify(0.9f); peers.add( entry ); } if (Logger.isEnabled()) Logger.log(new LogEvent(getTorrent(), LOGID, "TRTrackerClient: exported " + tracker_peer_cache.size() + " cached peers")); }finally{ tracker_peer_cache_mon.exit(); } return( res ); } protected int importTrackerCache( Map map ) { if ( !COConfigurationManager.getBooleanParameter("File.save.peers.enable")){ return( 0 ); } try{ if ( map == null ){ return( 0 ); } List peers = (List)map.get( "tracker_peers" ); if ( peers == null ){ return( 0 ); } try{ tracker_peer_cache_mon.enter(); for (int i=0;i<peers.size();i++){ Map peer = (Map)peers.get(i); byte[] src_bytes = (byte[])peer.get("src"); String peer_source = src_bytes==null?PEPeerSource.PS_BT_TRACKER:new String(src_bytes); String peer_ip_address = new String((byte[])peer.get("ip")); int peer_tcp_port = ((Long)peer.get("port")).intValue(); byte[] peer_peer_id = getAnonymousPeerId( peer_ip_address, peer_tcp_port ); Long l_protocol = (Long)peer.get( "prot" ); short protocol = l_protocol==null?DownloadAnnounceResultPeer.PROTOCOL_NORMAL:l_protocol.shortValue(); Long l_udp_port = (Long)peer.get("udpport"); int peer_udp_port = l_udp_port==null?0:l_udp_port.intValue(); Long l_http_port = (Long)peer.get("httpport"); int peer_http_port = l_http_port==null?0:l_http_port.intValue(); Long l_az_ver = (Long)peer.get("azver"); byte az_ver = l_az_ver==null?TRTrackerAnnouncer.AZ_TRACKER_VERSION_1:l_az_ver.byteValue(); //System.out.println( "recovered " + ip_address + ":" + port ); TRTrackerAnnouncerResponsePeerImpl entry = new TRTrackerAnnouncerResponsePeerImpl( peer_source, peer_peer_id, peer_ip_address, peer_tcp_port, peer_udp_port, peer_http_port, protocol, az_ver, (short)0 ); tracker_peer_cache.put( entry.getKey(), entry ); } return( tracker_peer_cache.size()); }finally{ tracker_peer_cache_mon.exit(); } }catch( Throwable e ){ Debug.printStackTrace( e ); return( tracker_peer_cache.size()); } } protected void addToTrackerCache( TRTrackerAnnouncerResponsePeerImpl[] peers ) { if ( !COConfigurationManager.getBooleanParameter("File.save.peers.enable")){ return; } int max = COConfigurationManager.getIntParameter( "File.save.peers.max", DEFAULT_PEERS_TO_CACHE ); // System.out.println( "max peers= " + max ); try{ tracker_peer_cache_mon.enter(); for (int i=0;i<peers.length;i++){ TRTrackerAnnouncerResponsePeerImpl peer = peers[i]; // remove and reinsert to maintain most recent last tracker_peer_cache.remove( peer.getKey()); tracker_peer_cache.put( peer.getKey(), peer ); } Iterator it = tracker_peer_cache.keySet().iterator(); if ( max > 0 ){ while ( tracker_peer_cache.size() > max ){ it.next(); it.remove(); } } }finally{ tracker_peer_cache_mon.exit(); } } public void removeFromTrackerResponseCache( String ip, int tcp_port ) { try{ tracker_peer_cache_mon.enter(); // create a fake peer so we can get the key TRTrackerAnnouncerResponsePeerImpl peer = new TRTrackerAnnouncerResponsePeerImpl( "", new byte[0], ip, tcp_port, 0, 0, (short)0, (byte)0, (short)0 ); if ( tracker_peer_cache.remove( peer.getKey()) != null ){ if (Logger.isEnabled()) Logger.log(new LogEvent( getTorrent(), LOGID, "Explicit removal of peer cache for " + ip + ":" + tcp_port )); } }finally{ tracker_peer_cache_mon.exit(); } } public static Map mergeResponseCache( Map map1, Map map2 ) { if ( map1 == null && map2 == null ){ return( new HashMap()); }else if ( map1 == null ){ return( map2 ); }else if ( map2 == null ){ return( map1 ); } Map res = new HashMap(); List peers = (List)map1.get( "tracker_peers" ); if ( peers == null ){ peers = new ArrayList(); } List p2 = (List)map2.get( "tracker_peers" ); if ( p2 != null ){ if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "TRTrackerClient: merged peer sets: p1 = " + peers.size() + ", p2 = " + p2.size())); for (int i=0;i<p2.size();i++){ peers.add( p2.get( i )); } } res.put( "tracker_peers", peers ); return( res ); } protected abstract int getPeerCacheLimit(); protected TRTrackerAnnouncerResponsePeer[] getPeersFromCache( int num_want ) { int limit = getPeerCacheLimit(); if ( limit <= 0 ){ return( new TRTrackerAnnouncerResponsePeer[0] ); } // limit peers returned to avoid multi-tracker torrents from getting swamped // by out-of-date peers from a failed tracker num_want = Math.min( limit, num_want ); try{ tracker_peer_cache_mon.enter(); TRTrackerAnnouncerResponsePeerImpl[] res; if ( tracker_peer_cache.size() <= num_want ){ res = new TRTrackerAnnouncerResponsePeerImpl[tracker_peer_cache.size()]; tracker_peer_cache.values().toArray( res ); }else{ res = new TRTrackerAnnouncerResponsePeerImpl[num_want]; Iterator it = tracker_peer_cache.keySet().iterator(); // take 'em out and put them back in so we cycle through the peers // over time for (int i=0;i<num_want;i++){ String key = (String)it.next(); res[i] = (TRTrackerAnnouncerResponsePeerImpl)tracker_peer_cache.get(key); it.remove(); } for (int i=0;i<num_want;i++){ tracker_peer_cache.put( res[i].getKey(), res[i] ); } } if (Logger.isEnabled()){ for (int i=0;i<res.length;i++){ Logger.log(new LogEvent(getTorrent(), LOGID, "CACHED PEER: " + res[i].getString())); } Logger.log(new LogEvent(getTorrent(), LOGID, "TRTrackerClient: returned " + res.length + " cached peers")); } cache_peers_used += res.length; return( res ); }finally{ tracker_peer_cache_mon.exit(); } } public TrackerPeerSource getCacheTrackerPeerSource() { return( new TrackerPeerSourceAdapter() { public String getName() { return( MessageText.getString( "tps.tracker.cache1", new String[]{ String.valueOf( cache_peers_used )})); } public int getPeers() { return( tracker_peer_cache.size() ); } }); } protected void informResponse( TRTrackerAnnouncerHelper helper, TRTrackerAnnouncerResponse response ) { listeners.dispatch( LDT_TRACKER_RESPONSE, response ); } protected void informURLRefresh() { listeners.dispatch( LDT_URL_REFRESH, null ); } public void addListener( TRTrackerAnnouncerListener l ) { listeners.addListener( l ); } public void removeListener( TRTrackerAnnouncerListener l ) { listeners.removeListener(l); } public interface Helper { public byte[] getPeerID(); public String getTrackerKey(); public int getUDPKey(); public void addToTrackerCache( TRTrackerAnnouncerResponsePeerImpl[] peers ); public TRTrackerAnnouncerResponsePeer[] getPeersFromCache( int num_want ); public void setTrackerResponseCache( Map map ); public void removeFromTrackerResponseCache( String ip, int tcpPort ); public Map getTrackerResponseCache(); public void informResponse( TRTrackerAnnouncerHelper helper, TRTrackerAnnouncerResponse response ); public void informURLChange( URL old_url, URL new_url, boolean explicit ); public void informURLRefresh(); public void addListener( TRTrackerAnnouncerListener l ); public void removeListener( TRTrackerAnnouncerListener l ); } }