/*
* Created on Aug 28, 2010
* Created by Paul Gardner
*
* Copyright 2010 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.core.networkmanager.impl.utp;
import java.nio.ByteBuffer;
import java.util.List;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.peer.impl.PEPeerControl;
import org.gudy.azureus2.core3.util.AEGenericCallback;
import org.gudy.azureus2.core3.util.AERunnable;
import org.gudy.azureus2.core3.util.AsyncDispatcher;
import org.gudy.azureus2.core3.util.Debug;
import com.aelitis.azureus.core.networkmanager.NetworkConnection;
import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.networkmanager.TransportEndpoint;
import com.aelitis.azureus.core.networkmanager.impl.ProtocolDecoder;
import com.aelitis.azureus.core.networkmanager.impl.TransportCryptoManager;
import com.aelitis.azureus.core.networkmanager.impl.TransportHelperFilter;
import com.aelitis.azureus.core.networkmanager.impl.TransportHelperFilterTransparent;
import com.aelitis.azureus.core.networkmanager.impl.TransportImpl;
public class
UTPTransport
extends TransportImpl
{
private static final LogIDs LOGID = LogIDs.NET;
private static AsyncDispatcher dispatcher = new AsyncDispatcher( "utp:condisp" );
private UTPConnectionManager manager;
private ProtocolEndpointUTP endpoint;
private boolean connect_with_crypto;
private boolean fallback_allowed;
private byte[][] shared_secrets;
private int fallback_count;
private int transport_mode = TRANSPORT_MODE_NORMAL;
private boolean connected;
private boolean cp_pending;
private ByteBuffer cp_initial_data;
private ConnectListener cp_listener;
private volatile boolean closed;
protected
UTPTransport(
UTPConnectionManager _manager,
ProtocolEndpointUTP _endpoint,
boolean _use_crypto,
boolean _allow_fallback,
byte[][] _shared_secrets )
{
manager = _manager;
endpoint = _endpoint;
connect_with_crypto = _use_crypto;
shared_secrets = _shared_secrets;
fallback_allowed = _allow_fallback;
}
protected
UTPTransport(
UTPConnectionManager _manager,
ProtocolEndpointUTP _endpoint,
TransportHelperFilter _filter )
{
manager = _manager;
endpoint = _endpoint;
setFilter( _filter );
}
public boolean
isTCP()
{
return( false );
}
public String
getProtocol()
{
return( "uTP" );
}
public TransportEndpoint
getTransportEndpoint()
{
return( new TransportEndpointUTP( endpoint ));
}
public int
getMssSize()
{
return( UTPNetworkManager.getUdpMssSize());
}
public String
getDescription()
{
return( endpoint.getAddress().toString());
}
public void
setTransportMode(
int mode )
{
transport_mode = mode;
}
public int
getTransportMode()
{
return( transport_mode );
}
public void
connectOutbound(
final ByteBuffer initial_data,
final ConnectListener listener,
final int priority )
{
if ( !UTPNetworkManager.UTP_OUTGOING_ENABLED ){
listener.connectFailure( new Throwable( "Outgoing uTP connections disabled" ));
return;
}
if ( closed ){
listener.connectFailure( new Throwable( "Connection already closed" ));
return;
}
if( getFilter() != null ){
listener.connectFailure( new Throwable( "Already connected" ));
return;
}
if ( COConfigurationManager.getBooleanParameter( "Proxy.Data.Enable" )){
listener.connectFailure( new Throwable( "uTP proxy connection not supported" ));
return;
}
int time = listener.connectAttemptStarted( -1 );
if ( time != -1 ){
Debug.out( "uTP connect time override not supported" );
}
UTPTransportHelper helper = null;
try{
helper = new UTPTransportHelper( manager, endpoint.getAddress(), this );
final UTPTransportHelper f_helper = helper;
if ( connect_with_crypto ){
TransportCryptoManager.getSingleton().manageCrypto(
helper,
shared_secrets,
false,
initial_data,
new TransportCryptoManager.HandshakeListener()
{
public void
handshakeSuccess(
ProtocolDecoder decoder,
ByteBuffer remaining_initial_data )
{
TransportHelperFilter filter = decoder.getFilter();
setFilter( filter );
connectedOutbound( remaining_initial_data, listener );
}
public void
handshakeFailure(
Throwable failure_msg )
{
if ( fallback_allowed &&
NetworkManager.OUTGOING_HANDSHAKE_FALLBACK_ALLOWED &&
!closed ){
if ( Logger.isEnabled() ){
Logger.log(new LogEvent(LOGID, "crypto handshake failure [" +failure_msg.getMessage()+ "], attempting non-crypto fallback." ));
}
fallback_count++;
connect_with_crypto = false;
close( f_helper, "Handshake failure and retry" );
closed = false;
if ( initial_data != null ){
initial_data.position( 0 );
}
connectOutbound( initial_data, listener, priority );
}else{
close( f_helper, "Handshake failure" );
listener.connectFailure( failure_msg );
}
}
public void
gotSecret(
byte[] session_secret )
{
}
public int
getMaximumPlainHeaderLength()
{
throw( new RuntimeException()); // this is outgoing
}
public int
matchPlainHeader(
ByteBuffer buffer )
{
throw( new RuntimeException()); // this is outgoing
}
});
}else{
setFilter( new TransportHelperFilterTransparent( helper, false ));
// wait until we are actually connected before reporting this
boolean already_connected = true;
synchronized( this ){
already_connected = connected;
if ( !already_connected ){
cp_pending = true;
cp_initial_data = initial_data;
cp_listener = listener;
}
}
if ( already_connected ){
connectedOutbound( initial_data, listener );
}
}
}catch( Throwable e ){
Debug.printStackTrace(e);
if ( helper != null ){
helper.close( Debug.getNestedExceptionMessage( e ));
}
listener.connectFailure( e );
}
}
protected void
connected()
{
final ByteBuffer initial_data;
final ConnectListener listener;
synchronized( this ){
connected = true;
if ( cp_pending ){
initial_data = cp_initial_data;
listener = cp_listener;
cp_pending = false;
cp_initial_data = null;
cp_listener = null;
}else{
return;
}
}
// need to get off of this thread due to deadlock potential
dispatcher.dispatch(
new AERunnable()
{
public void
runSupport()
{
connectedOutbound( initial_data, listener );
}
});
}
protected void
closed()
{
final ConnectListener listener;
synchronized( this ){
if ( cp_pending ){
cp_pending = false;
listener = cp_listener;
cp_listener = null;
}else{
return;
}
}
if ( listener != null ){
// need to get off of this thread due to deadlock potential
dispatcher.dispatch(
new AERunnable()
{
public void
runSupport()
{
listener.connectFailure( new Throwable( "Connection closed" ));
}
});
}
}
protected void
connectedOutbound(
ByteBuffer remaining_initial_data,
ConnectListener listener )
{
TransportHelperFilter filter = getFilter();
if ( Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "Outgoing uTP stream to " + endpoint.getAddress() + " established, type = " + (filter==null?"<unknown>":filter.getName(false))));
}
if ( closed ){
if ( filter != null ){
filter.getHelper().close( "Connection closed" );
setFilter( null );
}
listener.connectFailure( new Throwable( "Connection closed" ));
}else{
connectedOutbound();
listener.connectSuccess( this, remaining_initial_data );
}
}
private void
close(
UTPTransportHelper helper,
String reason )
{
helper.close( reason );
close( reason );
}
public void
close(
String reason )
{
closed = true;
readyForRead( false );
readyForWrite( false );
TransportHelperFilter filter = getFilter();
if ( filter != null ){
filter.getHelper().close( reason );
setFilter( null );
}
closed();
}
public boolean
isClosed()
{
return( closed );
}
public void
bindConnection(
NetworkConnection connection )
{
if ( manager.preferUTP()){
final Object[] existing = { null };
existing[0] =
connection.setUserData(
"RoutedCallback",
new AEGenericCallback()
{
public Object
invoke(
Object arg )
{
try{
PEPeerControl control = (PEPeerControl)arg;
List<PEPeer> peers = control.getPeers( endpoint.getAddress().getAddress().getHostAddress());
for ( PEPeer peer: peers ){
if ( !peer.isIncoming() &&
peer.getTCPListenPort() == endpoint.getAddress().getPort()){
manager.log( "Overriding existing connection to " + endpoint.getAddress());
control.removePeer( peer, "Replacing outgoing with incoming uTP connection" );
}
}
return( null );
}finally{
if ( existing[0] instanceof AEGenericCallback ){
((AEGenericCallback)existing[0]).invoke( arg );
}
}
}
});
}
}
}