/* * 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.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.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.plugins.download.DownloadAnnounceResultPeer; /** * @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; protected ListenerManager listeners = ListenerManager.createManager( "TrackerClient:ListenDispatcher", new ListenerManagerDispatcher() { public void dispatch( Object _listener, int type, Object value ) { TRTrackerAnnouncerListener listener = (TRTrackerAnnouncerListener)_listener; 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 TOTorrent torrent; protected TRTrackerAnnouncerImpl( TOTorrent _torrent ) { torrent = _torrent; } // 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 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 ); } protected int importTrackerCache( Map map ) { if ( torrent.getPrivate() || !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 ( torrent.getPrivate() || !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 TRTrackerAnnouncerResponsePeer[] getPeersFromCache( int num_want ) { if ( torrent.getPrivate()){ // we don't use cached peers for private torrents return( new TRTrackerAnnouncerResponsePeer[0] ); } 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")); } return( res ); }finally{ tracker_peer_cache_mon.exit(); } } public void addListener( TRTrackerAnnouncerListener l ) { listeners.addListener( l ); } public void removeListener( TRTrackerAnnouncerListener l ) { listeners.removeListener(l); } }