/*
* Created on Feb 15, 2008
* Created by Paul Gardner
*
* Copyright 2008 Vuze, Inc. 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; version 2 of the License only.
*
* 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.
*/
package com.aelitis.azureus.plugins.net.netstatus;
import java.net.InetSocketAddress;
import java.util.*;
import org.gudy.azureus2.core3.util.AERunnable;
import org.gudy.azureus2.core3.util.AESemaphore;
import org.gudy.azureus2.core3.util.AEThread2;
import org.gudy.azureus2.core3.util.BDecoder;
import org.gudy.azureus2.core3.util.BEncoder;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.DelayedEvent;
import org.gudy.azureus2.core3.util.HashWrapper;
import org.gudy.azureus2.core3.util.SimpleTimer;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.core3.util.TimerEvent;
import org.gudy.azureus2.core3.util.TimerEventPerformer;
import org.gudy.azureus2.core3.util.TimerEventPeriodic;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.ddb.DistributedDatabase;
import org.gudy.azureus2.plugins.ddb.DistributedDatabaseContact;
import org.gudy.azureus2.plugins.ddb.DistributedDatabaseException;
import org.gudy.azureus2.plugins.ddb.DistributedDatabaseKey;
import org.gudy.azureus2.plugins.ddb.DistributedDatabaseProgressListener;
import org.gudy.azureus2.plugins.ddb.DistributedDatabaseTransferHandler;
import org.gudy.azureus2.plugins.ddb.DistributedDatabaseTransferType;
import org.gudy.azureus2.plugins.ddb.DistributedDatabaseValue;
import com.aelitis.azureus.core.dht.DHT;
import com.aelitis.azureus.core.dht.transport.DHTTransportContact;
import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPNetworkManager;
import com.aelitis.azureus.plugins.dht.DHTPlugin;
import com.aelitis.azureus.plugins.net.netstatus.NetStatusProtocolTesterBT.Session;
public class
NetStatusProtocolTester
implements DistributedDatabaseTransferHandler
{
private static final int REQUEST_HISTORY_MAX = 64;
private static final int MAX_ACTIVE_TESTS = 3;
private static final int MAX_TEST_TIME = 120*1000;
private static final int TEST_TYPE_BT = 1;
private static final int VERSION_INITIAL = 1;
private static final int CURRENT_VERSION = VERSION_INITIAL;
private static final int BT_MAX_SLAVES = 8;
private NetStatusPlugin plugin;
private PluginInterface plugin_interface;
private DistributedDatabase ddb;
private DHTPlugin dht_plugin;
private testXferType transfer_type = new testXferType();
private Map request_history =
new LinkedHashMap(REQUEST_HISTORY_MAX,0.75f,true)
{
protected boolean
removeEldestEntry(
Map.Entry eldest)
{
return size() > REQUEST_HISTORY_MAX;
}
};
private List active_tests = new ArrayList();
private TimerEventPeriodic timer_event = null;
protected
NetStatusProtocolTester(
NetStatusPlugin _plugin,
PluginInterface _plugin_interface )
{
plugin = _plugin;
plugin_interface = _plugin_interface;
try{
PluginInterface dht_pi = plugin_interface.getPluginManager().getPluginInterfaceByClass( DHTPlugin.class );
if ( dht_pi != null ){
dht_plugin = (DHTPlugin)dht_pi.getPlugin();
}
ddb = plugin_interface.getDistributedDatabase();
ddb.addTransferHandler( transfer_type, this );
log( "DDB transfer type registered" );
}catch( Throwable e ){
log( "DDB transfer type registration failed", e );
}
}
public NetStatusProtocolTesterBT
runTest(
final NetStatusProtocolTesterListener listener )
{
return( runTest( "", listener ));
}
public NetStatusProtocolTesterBT
runTest(
String test_address,
final NetStatusProtocolTesterListener listener )
{
final NetStatusProtocolTesterBT bt_tester = new NetStatusProtocolTesterBT( this, true );
bt_tester.addListener( listener );
bt_tester.start();
addToActive( bt_tester );
try{
if ( test_address.length() == 0 ){
DHT[] dhts = dht_plugin.getDHTs();
DHT target_dht = null;
int target_network = Constants.isCVSVersion()?DHT.NW_CVS:DHT.NW_MAIN;
for (int i=0;i<dhts.length;i++){
if ( dhts[i].getTransport().getNetwork() == target_network ){
target_dht = dhts[i];
break;
}
}
if ( target_dht == null ){
listener.logError( "Distributed database unavailable" );
}else{
DHTTransportContact[] contacts = target_dht.getTransport().getReachableContacts();
final List f_contacts = new ArrayList(Arrays.asList(contacts));
final int[] ok = new int[]{ 0 };
final int num_threads = Math.min( BT_MAX_SLAVES, contacts.length );
listener.log( "Searching " + contacts.length + " contacts for " + num_threads + " test targets" );
final AESemaphore sem = new AESemaphore( "NetStatusProbe" );
for (int i=0;i<num_threads;i++){
new AEThread2( "NetStatusProbe", true )
{
public void
run()
{
try{
while( !bt_tester.isDestroyed()){
DHTTransportContact contact = null;
synchronized( ok ){
if ( ok[0] < num_threads && f_contacts.size() > 0 ){
contact = (DHTTransportContact)f_contacts.remove(0);
}
}
if ( contact == null ){
break;
}
try{
DistributedDatabaseContact ddb_contact = ddb.importContact( contact.getAddress());
if ( tryTest( bt_tester, ddb_contact )){
synchronized( ok ){
ok[0]++;
}
}
}catch( Throwable e ){
listener.logError( "Contact import for " + contact.getName() + " failed", e );
}
}
}finally{
sem.release();
}
}
}.start();
}
for (int i=0;i<num_threads;i++){
sem.reserve();
}
listener.log( "Searching complete, " + ok[0] + " targets found" );
}
}else{
String[] bits = test_address.split( ":" );
if ( bits.length != 2 ){
log( "Invalid address - use <host>:<port> " );
return( bt_tester );
}
InetSocketAddress address = new InetSocketAddress( bits[0].trim(), Integer.parseInt( bits[1].trim()));
DistributedDatabaseContact contact = ddb.importContact( address );
tryTest( bt_tester, contact );
}
}catch( Throwable e ){
listener.logError( "Test failed", e );
}finally{
bt_tester.addListener(
new NetStatusProtocolTesterListener()
{
public void
sessionAdded(
Session session)
{
}
public void
complete(
NetStatusProtocolTesterBT tester )
{
removeFromActive( tester );
}
public void
log(
String str )
{
}
public void
logError(
String str )
{
}
public void
logError(
String str,
Throwable e )
{
}
});
bt_tester.setOutboundConnectionsComplete();
new DelayedEvent(
"NetStatus:killer",
10*1000,
new AERunnable()
{
public void
runSupport()
{
listener.log( "Destroying tester" );
bt_tester.destroy();
}
});
}
return( bt_tester );
}
protected boolean
tryTest(
NetStatusProtocolTesterBT bt_tester,
DistributedDatabaseContact contact )
{
boolean use_crypto = NetworkManager.getCryptoRequired( NetworkManager.CRYPTO_OVERRIDE_NONE );
log( "Trying test to " + contact.getName());
Map request = new HashMap();
request.put( "v", new Long( CURRENT_VERSION ));
request.put( "t", new Long( TEST_TYPE_BT ));
request.put( "h", bt_tester.getServerHash());
request.put( "c", new Long( use_crypto?1:0 ));
Map reply = sendRequest( contact, request );
byte[] server_hash = reply==null?null:(byte[])reply.get( "h" );
if ( server_hash != null ){
log( " " + contact.getName() + " accepted test" );
bt_tester.testOutbound( adjustLoopback( contact.getAddress()), server_hash, use_crypto );
return( true );
}else{
log( " " + contact.getName() + " declined test" );
return( false );
}
}
protected InetSocketAddress
adjustLoopback(
InetSocketAddress address )
{
InetSocketAddress local = dht_plugin.getLocalAddress().getAddress();
if ( local.getAddress().getHostAddress().equals( address.getAddress().getHostAddress())){
return( new InetSocketAddress( "127.0.0.1", address.getPort()));
}else{
return( address );
}
}
protected Map
sendRequest(
DistributedDatabaseContact contact,
Map request )
{
try{
log( "Sending DDB request to " + contact.getName() + " - " + request );
DistributedDatabaseKey key = ddb.createKey( BEncoder.encode( request ));
DistributedDatabaseValue value =
contact.read(
new DistributedDatabaseProgressListener()
{
public void
reportSize(
long size )
{
}
public void
reportActivity(
String str )
{
}
public void
reportCompleteness(
int percent )
{
}
},
transfer_type,
key,
10000 );
if ( value == null ){
return( null );
}
Map reply = BDecoder.decode((byte[])value.getValue( byte[].class ));
log( " received reply - " + reply );
return( reply );
}catch( Throwable e ){
log( "sendRequest failed", e );
return( null );
}
}
protected Map
receiveRequest(
InetSocketAddress originator,
Map request )
{
Map reply = new HashMap();
Long test_type = (Long)request.get( "t" );
reply.put( "v", new Long( CURRENT_VERSION ));
if ( test_type != null ){
if ( test_type.intValue() == TEST_TYPE_BT ){
TCPNetworkManager tcp_man = TCPNetworkManager.getSingleton();
InetSocketAddress adjusted_originator = adjustLoopback( originator );
boolean test = adjusted_originator.getAddress().isLoopbackAddress();
if ( test ||
( tcp_man.isTCPListenerEnabled() &&
tcp_man.getTCPListeningPortNumber() == ddb.getLocalContact().getAddress().getPort() &&
SystemTime.getCurrentTime() - tcp_man.getLastIncomingNonLocalConnectionTime() <= 24*60*60*1000 )){
byte[] their_hash = (byte[])request.get( "h" );
if ( their_hash != null ){
NetStatusProtocolTesterBT bt_tester;
synchronized( active_tests ){
if ( active_tests.size() > MAX_ACTIVE_TESTS ){
log( "Too many active tests" );
return( reply );
}else{
bt_tester = new NetStatusProtocolTesterBT( this, false );
bt_tester.start();
addToActive( bt_tester );
}
}
Long l_crypto = (Long)request.get( "c" );
boolean use_crypto = l_crypto!=null&&l_crypto.longValue()==1;
bt_tester.testOutbound( adjusted_originator, their_hash, use_crypto );
reply.put( "h", bt_tester.getServerHash());
}
}
}
}
return( reply );
}
protected void
addToActive(
NetStatusProtocolTesterBT tester )
{
synchronized( active_tests ){
active_tests.add( tester );
if ( timer_event == null ){
timer_event =
SimpleTimer.addPeriodicEvent(
"NetStatusProtocolTester:timer",
30*1000,
new TimerEventPerformer()
{
public void
perform(
TimerEvent event )
{
long now = SystemTime.getCurrentTime();
List to_remove = new ArrayList();
synchronized( active_tests ){
for (int i=0;i<active_tests.size();i++){
NetStatusProtocolTesterBT tester = (NetStatusProtocolTesterBT)active_tests.get(i);
long start = tester.getStartTime( now );
if ( now - start > MAX_TEST_TIME ){
to_remove.add( tester );
}
}
}
for ( int i=0;i<to_remove.size();i++ ){
removeFromActive( (NetStatusProtocolTesterBT)to_remove.get(i));
}
}
});
}
}
}
protected void
removeFromActive(
NetStatusProtocolTesterBT tester )
{
tester.destroy();
synchronized( active_tests ){
active_tests.remove( tester );
if ( active_tests.size() == 0 ){
if ( timer_event != null ){
timer_event.cancel();
timer_event = null;
}
}
}
}
public DistributedDatabaseValue
read(
DistributedDatabaseContact contact,
DistributedDatabaseTransferType type,
DistributedDatabaseKey ddb_key )
throws DistributedDatabaseException
{
Object o_key = ddb_key.getKey();
try{
byte[] key = (byte[])o_key;
HashWrapper hw = new HashWrapper( key );
synchronized( request_history ){
if ( request_history.containsKey( hw )){
return( null );
}
request_history.put( hw, "" );
}
Map request = BDecoder.decode( (byte[])o_key);
log( "Received DDB request from " + contact.getName() + " - " + request );
Map result = receiveRequest( contact.getAddress(), request );
return( ddb.createValue( BEncoder.encode( result )));
}catch( Throwable e ){
log( "DDB read failed", e );
return( null );
}
}
public void
write(
DistributedDatabaseContact contact,
DistributedDatabaseTransferType type,
DistributedDatabaseKey key,
DistributedDatabaseValue value )
throws DistributedDatabaseException
{
throw( new DistributedDatabaseException( "not supported" ));
}
public void
log(
String str )
{
plugin.log( str );
}
public void
log(
String str,
Throwable e )
{
plugin.log( str, e );
}
protected class
testXferType
implements DistributedDatabaseTransferType
{
}
}