package org.limewire.core.impl.friend;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.limewire.core.impl.friend.FriendFirewalledAddressConnector.PushedSocketConnectObserver;
import org.limewire.friend.api.FriendException;
import org.limewire.friend.api.FriendPresence;
import org.limewire.friend.api.feature.ConnectBackRequestFeature;
import org.limewire.friend.api.feature.FeatureTransport;
import org.limewire.friend.impl.address.FriendAddress;
import org.limewire.friend.impl.address.FriendAddressResolver;
import org.limewire.friend.impl.address.FriendFirewalledAddress;
import org.limewire.io.Address;
import org.limewire.io.Connectable;
import org.limewire.io.GUID;
import org.limewire.net.ConnectBackRequest;
import org.limewire.net.SocketsManager;
import org.limewire.net.address.FirewalledAddress;
import org.limewire.nio.AbstractNBSocket;
import org.limewire.nio.observer.ConnectObserver;
import org.limewire.rudp.AbstractNBSocketChannel;
import org.limewire.rudp.UDPSelectorProvider;
import org.limewire.util.BaseTestCase;
import org.limewire.util.MatchAndCopy;
import com.google.inject.Provider;
import com.limegroup.gnutella.NetworkManager;
import com.limegroup.gnutella.SocketProcessor;
import com.limegroup.gnutella.downloader.PushDownloadManager;
import com.limegroup.gnutella.downloader.PushedSocketHandlerRegistry;
public class FriendFirewalledAddressConnectorTest extends BaseTestCase {
public FriendFirewalledAddressConnectorTest(String name) {
super(name);
}
/**
* Test that ensures the register methods will correctly link the connector to the sockets manager and socket
* handler register.
*/
public void testRegister() {
Mockery context = new Mockery();
final SocketsManager socketsManager = context.mock(SocketsManager.class);
final PushedSocketHandlerRegistry pushedSocketHandlerRegistry = context.mock(PushedSocketHandlerRegistry.class);
final FriendFirewalledAddressConnector connector
= new FriendFirewalledAddressConnector(null, null, null, null, null, null);
context.checking(new Expectations() {
{ exactly(1).of(socketsManager).registerConnector(connector);
exactly(1).of(pushedSocketHandlerRegistry).register(connector);
}});
connector.register(socketsManager);
connector.register(pushedSocketHandlerRegistry);
context.assertIsSatisfied();
}
/**
* Tries the can connect function under various conditions.
*/
public void testCanConnect() {
Mockery context = new Mockery() {
{ setImposteriser(ClassImposteriser.INSTANCE);
}};
final PushDownloadManager pushDownloadManager = context.mock(PushDownloadManager.class);
final Address adressNotXmpp = context.mock(Address.class);
final FriendFirewalledAddress adressCantConnect = context.mock(FriendFirewalledAddress.class);
final FriendFirewalledAddress adressCanConnect = context.mock(FriendFirewalledAddress.class);
final Address fwAddressCantConnect = context.mock(FirewalledAddress.class);
final Address fwAddressCanConnect = context.mock(FirewalledAddress.class);
final FriendFirewalledAddressConnector connector
= new FriendFirewalledAddressConnector(null, pushDownloadManager, null, null, null, null);
context.checking(new Expectations() {
{ allowing(adressCantConnect).getFirewalledAddress();
will(returnValue(fwAddressCantConnect));
allowing(adressCanConnect).getFirewalledAddress();
will(returnValue(fwAddressCanConnect));
atLeast(1).of(pushDownloadManager).canConnect(fwAddressCantConnect);
will(returnValue(false));
atLeast(1).of(pushDownloadManager).canConnect(fwAddressCanConnect);
will(returnValue(true));
}});
assertFalse(connector.canConnect(adressNotXmpp));
assertFalse(connector.canConnect(adressCantConnect));
assertTrue(connector.canConnect(adressCanConnect));
context.assertIsSatisfied();
}
/**
* Ensure ready sockets are delegated to the correct observers.
*/
public void testAcceptPushedSocket() throws IOException {
Mockery context = new Mockery() {
{ setImposteriser(ClassImposteriser.INSTANCE);
}};
final InetAddress inetAddress = context.mock(InetAddress.class);
final ConnectObserver connectObserverA = context.mock(ConnectObserver.class);
final FirewalledAddress fwAddressA = context.mock(FirewalledAddress.class);
final byte[] guidA = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16 };
final Socket socketA = context.mock(Socket.class);
final Connectable connectableA = context.mock(Connectable.class);
final ConnectObserver connectObserverB = context.mock(ConnectObserver.class);
final FirewalledAddress fwAddressB = context.mock(FirewalledAddress.class);
final byte[] guidB = new byte[] { 1, 2, 3, 4, 5, 6, '\'', 'n', 9, 10, 11, 12, 13, 14, 15,
16 };
final Socket socketB = context.mock(Socket.class);
final Connectable connectableB = context.mock(Connectable.class);
final Socket socketAwithIOE = context.mock(Socket.class);
final Socket socketAwithBadAddr = context.mock(Socket.class);
final InputStream isWithIOE = context.mock(InputStream.class);
final OutputStream osWithIOE = context.mock(OutputStream.class);
final FriendFirewalledAddressConnector connector
= new FriendFirewalledAddressConnector(null, null, null, null, null, null);
context.checking(new Expectations() {
{
// Non critical actions
allowing(fwAddressA).getClientGuid();
will(returnValue(new GUID(guidA)));
allowing(fwAddressA).getPublicAddress();
will(returnValue(connectableA));
allowing(socketA).getInetAddress();
will(returnValue(inetAddress));
allowing(fwAddressB).getClientGuid();
will(returnValue(new GUID(guidB)));
allowing(fwAddressB).getPublicAddress();
will(returnValue(connectableB));
allowing(socketB).getInetAddress();
will(returnValue(inetAddress));
allowing(socketAwithBadAddr).getInetAddress();
will(returnValue(null));
allowing(socketAwithIOE).getInetAddress();
will(returnValue(inetAddress));
allowing(connectableA).getAddress();
will(returnValue("127.0.0.1"));
allowing(connectableA).getPort();
will(returnValue(80));
allowing(connectableA).getInetAddress();
will(returnValue(inetAddress));
allowing(connectableB).getAddress();
will(returnValue("127.0.0.1"));
allowing(connectableB).getPort();
will(returnValue(80));
allowing(connectableB).getInetAddress();
will(returnValue(inetAddress));
// The bad socket should be closed
allowing(socketAwithIOE).getInputStream();
will(returnValue(isWithIOE));
allowing(socketAwithIOE).getOutputStream();
will(returnValue(osWithIOE));
exactly(1).of(socketAwithIOE).close();
exactly(1).of(isWithIOE).close();
exactly(1).of(osWithIOE).close();
// Assertions
// Forced for testing an IOE
exactly(1).of(connectObserverA).handleConnect(socketAwithIOE);
will(throwException(new IOException("forced exception")));
exactly(1).of(connectObserverA).handleConnect(socketA);
exactly(1).of(connectObserverB).handleConnect(socketB);
}});
// No observers have been set therefore nothing should be grabbed
assertFalse(connector.acceptPushedSocket("blah", 10, guidA, socketA));
connector.observers.add(createRandomObserver(context));
connector.observers.add(createRandomObserver(context));
// No matching observers have been set therefore nothing should be grabbed
assertFalse(connector.acceptPushedSocket("blah", 10, guidA, socketA));
PushedSocketConnectObserver pushedSocketConnectObserverA
= new PushedSocketConnectObserver(fwAddressA, connectObserverA);
connector.observers.add(pushedSocketConnectObserverA);
connector.observers.add(createRandomObserver(context));
connector.observers.add(new PushedSocketConnectObserver(fwAddressB, connectObserverB));
connector.observers.add(createRandomObserver(context));
// These should be handled correctly
assertTrue(connector.acceptPushedSocket("asdsad", -1, guidA, socketA));
assertTrue(connector.acceptPushedSocket("asdsadsad", 10, guidB, socketB));
// Repeat should fail
assertFalse(connector.acceptPushedSocket("asdsad", -1, guidA, socketA));
// Try for A with a bad address
pushedSocketConnectObserverA.acceptedOrFailed.set(false);
assertFalse(connector.acceptPushedSocket("blah", 10, guidA, socketAwithBadAddr));
// Try for A with a bad socket -- returns true, exception suppressed, and socket closed
pushedSocketConnectObserverA.acceptedOrFailed.set(false);
assertTrue(connector.acceptPushedSocket("blah", 10, guidA, socketAwithIOE));
context.assertIsSatisfied();
}
private PushedSocketConnectObserver createRandomObserver(Mockery context) throws IOException {
final ConnectObserver connectObserver = context.mock(ConnectObserver.class);
final FirewalledAddress fwAddress = context.mock(FirewalledAddress.class);
final byte[] guid = new byte[] { 'x', (byte) (Math.random()*Byte.MAX_VALUE), 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
'x' };
context.checking(new Expectations() {
{ allowing(fwAddress).getClientGuid();
will(returnValue(new GUID(guid)));
never(connectObserver).handleConnect(with(any(Socket.class)));
}});
return new PushedSocketConnectObserver(fwAddress, connectObserver);
}
/**
* Go through the simply success case of XMPPFirewalledAddressConnector::connect()
* and confirm the basic interactions.
* <p>
* After manually fire some of the exception handling code and make sure the events
* are processed correctly.
*/
@SuppressWarnings("unchecked")
public void testConnectSimple() throws Exception {
Mockery context = new Mockery() {
{ setImposteriser(ClassImposteriser.INSTANCE);
}};
final FriendAddressResolver friendAddressResolver = context.mock(FriendAddressResolver.class);
final NetworkManager networkManager = context.mock(NetworkManager.class);
final Provider<UDPSelectorProvider> udpSelectorProviderProvider = context.mock(Provider.class);
final ScheduledExecutorService backgroundExecutor = context.mock(ScheduledExecutorService.class);
final Provider<SocketProcessor> socketProcessorProvider = context.mock(Provider.class);
final ConnectObserver observer = context.mock(ConnectObserver.class);
final FriendFirewalledAddress address = context.mock(FriendFirewalledAddress.class);
final FirewalledAddress fwAddress = context.mock(FirewalledAddress.class);
final GUID guid = new GUID(new byte[] {'X','x',1,2,3,4,'.','.','.',5,6,9,'n', 10,'x','X'});
final FriendAddress friendAddress = context.mock(FriendAddress.class);
final Connectable publicConnectable = context.mock(Connectable.class);
final InetAddress inetAddr = context.mock(InetAddress.class);
final InetSocketAddress inetSocketAddr = context.mock(InetSocketAddress.class);
final UDPSelectorProvider udpSelectorProvider = context.mock(UDPSelectorProvider.class);
final AbstractNBSocketChannel socketChanel = context.mock(AbstractNBSocketChannel.class);
final AbstractNBSocket socket = context.mock(AbstractNBSocket.class);
final Socket socketForConnect = context.mock(Socket.class);
final IOException forcedIOE = new IOException("forced");
final SocketProcessor socketProcessor = context.mock(SocketProcessor.class);
final FriendPresence friendPresence = context.mock(FriendPresence.class);
final FeatureTransport<ConnectBackRequest> connectBackTransport = context.mock(FeatureTransport.class);
final MatchAndCopy<ConnectObserver> connectObserverCollector
= new MatchAndCopy<ConnectObserver>(ConnectObserver.class);
final MatchAndCopy<Runnable> runnableCollector = new MatchAndCopy<Runnable>(Runnable.class);
final FriendFirewalledAddressConnector connector
= new FriendFirewalledAddressConnector(friendAddressResolver, null, networkManager,
backgroundExecutor, udpSelectorProviderProvider, socketProcessorProvider);
context.checking(new Expectations() {
{
allowing(publicConnectable).getAddress();
will(returnValue("40.1.0.0"));
allowing(publicConnectable).getPort();
will(returnValue(427));
allowing(publicConnectable).getInetAddress();
will(returnValue(inetAddr));
allowing(publicConnectable).getInetSocketAddress();
will(returnValue(inetSocketAddr));
}});
final ConnectBackRequest connectBackRequest = new ConnectBackRequest(publicConnectable, guid, 407);
context.assertIsSatisfied();
context.checking(new Expectations() {
{ allowing(address).getFirewalledAddress();
will(returnValue(fwAddress));
allowing(fwAddress).getClientGuid();
will(returnValue(guid));
allowing(fwAddress).getPublicAddress();
will(returnValue(publicConnectable));
allowing(networkManager).getPublicAddress();
will(returnValue(publicConnectable));
allowing(address).getFriendAddress();
will(returnValue(friendAddress));
allowing(friendAddress).getFullId();
will(returnValue("403"));
allowing(networkManager).supportsFWTVersion();
will(returnValue(407));
allowing(udpSelectorProviderProvider).get();
will(returnValue(udpSelectorProvider));
allowing(udpSelectorProvider).openSocketChannel();
will(returnValue(socketChanel));
allowing(socketChanel).socket();
will(returnValue(socket));
allowing(socketProcessorProvider).get();
will(returnValue(socketProcessor));
// Assertions
exactly(1).of(networkManager).acceptedIncomingConnection();
will(returnValue(false));
one(friendAddressResolver).getPresence(friendAddress);
will(returnValue(friendPresence));
one(friendPresence).getTransport(ConnectBackRequestFeature.class);
will(returnValue(connectBackTransport));
one(connectBackTransport).sendFeature(friendPresence, connectBackRequest);
exactly(1).of(socket).connect(with(same(inetSocketAddr)),
with(any(Integer.class)), with(connectObserverCollector));
exactly(1).of(backgroundExecutor).schedule(with(runnableCollector),
with(any(Integer.class)), with((any(TimeUnit.class))));
exactly(1).of(observer).handleIOException(forcedIOE);
exactly(1).of(observer).handleIOException(with(any(ConnectException.class)));
exactly(1).of(observer).handleIOException(with(any(IOException.class)));
exactly(1).of(socketProcessor).processSocket(socketForConnect, "GIV");
}});
// Connect
connector.connect(address, observer);
// ...
assertNotEmpty(connector.observers);
PushedSocketConnectObserver pushedConnectObserver = connector.observers.get(0);
ConnectObserver connectObserver = connectObserverCollector.getLastMatch();
Runnable expirerRunnable = runnableCollector.getLastMatch();
// Make sure the observers are properly linked by simulating some events
connectObserver.shutdown();
pushedConnectObserver.acceptedOrFailed.set(false);
connectObserver.handleIOException(forcedIOE);
connectObserver.handleIOException(forcedIOE);
pushedConnectObserver.acceptedOrFailed.set(false);
connectObserver.handleConnect(socketForConnect);
// Force expire the connection. Ensures
// the event is captured and observer is removed
expirerRunnable.run();
assertEmpty(connector.observers);
context.assertIsSatisfied();
}
/**
* Attempt to connect using an invalid public ip. Ensure this is
* handled gracefully and an IOE is passed to the ConnectObserver.
*/
@SuppressWarnings("unchecked")
public void testConnectWithBadIpPort() throws IOException {
Mockery context = new Mockery() {
{ setImposteriser(ClassImposteriser.INSTANCE);
}};
final NetworkManager networkManager = context.mock(NetworkManager.class);
final Provider<UDPSelectorProvider> udpSelectorProviderProvider = context.mock(Provider.class);
final ScheduledExecutorService backgroundExecutor = context.mock(ScheduledExecutorService.class);
final Provider<SocketProcessor> socketProcessorProvider = context.mock(Provider.class);
final ConnectObserver observer = context.mock(ConnectObserver.class);
final FriendFirewalledAddress address = context.mock(FriendFirewalledAddress.class);
final FirewalledAddress fwAddress = context.mock(FirewalledAddress.class);
final GUID guid = new GUID(new byte[] {'X','x',1,2,3,4,'.','.','.',5,6,9,'n', 10,'x','X'});
final Connectable publicConnectable = context.mock(Connectable.class);
final InetAddress inetAddr = context.mock(InetAddress.class);
final InetSocketAddress inetSocketAddr = context.mock(InetSocketAddress.class);
final FriendFirewalledAddressConnector connector
= new FriendFirewalledAddressConnector(null, null, networkManager,
backgroundExecutor, udpSelectorProviderProvider, socketProcessorProvider);
context.checking(new Expectations() {
{ allowing(address).getFirewalledAddress();
will(returnValue(fwAddress));
allowing(fwAddress).getClientGuid();
will(returnValue(guid));
allowing(fwAddress).getPublicAddress();
will(returnValue(publicConnectable));
allowing(networkManager).getPublicAddress();
will(returnValue(publicConnectable));
allowing(publicConnectable).getAddress();
will(returnValue("401.0.0.0"));
allowing(publicConnectable).getPort();
will(returnValue(-404));
allowing(publicConnectable).getInetAddress();
will(returnValue(inetAddr));
allowing(publicConnectable).getInetSocketAddress();
will(returnValue(inetSocketAddr));
exactly(1).of(observer).handleIOException(with(any(ConnectException.class)));
}});
// Connect
connector.connect(address, observer);
context.assertIsSatisfied();
}
/**
* Connect when is there is no incoming connection and an send failure.
*/
@SuppressWarnings("unchecked")
public void testConnectWithFails() throws Exception {
Mockery context = new Mockery() {
{ setImposteriser(ClassImposteriser.INSTANCE);
}};
final FriendAddressResolver friendAddressResolver = context.mock(FriendAddressResolver.class);
final NetworkManager networkManager = context.mock(NetworkManager.class);
final Provider<UDPSelectorProvider> udpSelectorProviderProvider = context.mock(Provider.class);
final ScheduledExecutorService backgroundExecutor = context.mock(ScheduledExecutorService.class);
final Provider<SocketProcessor> socketProcessorProvider = context.mock(Provider.class);
final PushDownloadManager pushDownloadManager = context.mock(PushDownloadManager.class);
final ConnectObserver observer = context.mock(ConnectObserver.class);
final FriendFirewalledAddress address = context.mock(FriendFirewalledAddress.class);
final FirewalledAddress fwAddress = context.mock(FirewalledAddress.class);
final GUID guid = new GUID(new byte[] {'X','x',1,2,3,4,'.','.','.',5,6,9,'n', 10,'x','X'});
final FriendAddress friendAddress = context.mock(FriendAddress.class);
final Connectable publicConnectable = context.mock(Connectable.class);
final InetAddress inetAddr = context.mock(InetAddress.class);
final InetSocketAddress inetSocketAddr = context.mock(InetSocketAddress.class);
final FriendPresence friendPresence = context.mock(FriendPresence.class);
final FeatureTransport<ConnectBackRequest> connectBackTransport = context.mock(FeatureTransport.class);
final FriendFirewalledAddressConnector connector
= new FriendFirewalledAddressConnector(friendAddressResolver, pushDownloadManager, networkManager,
backgroundExecutor, udpSelectorProviderProvider, socketProcessorProvider);
context.checking(new Expectations() {
{
allowing(publicConnectable).getAddress();
will(returnValue("40.1.0.0"));
allowing(publicConnectable).getPort();
will(returnValue(427));
allowing(publicConnectable).getInetAddress();
will(returnValue(inetAddr));
allowing(publicConnectable).getInetSocketAddress();
will(returnValue(inetSocketAddr));
}});
final ConnectBackRequest connectBackRequest = new ConnectBackRequest(publicConnectable, guid, 0);
context.assertIsSatisfied();
context.checking(new Expectations() {
{ allowing(address).getFirewalledAddress();
will(returnValue(fwAddress));
allowing(fwAddress).getClientGuid();
will(returnValue(guid));
allowing(fwAddress).getPublicAddress();
will(returnValue(publicConnectable));
allowing(networkManager).getPublicAddress();
will(returnValue(publicConnectable));
allowing(address).getFriendAddress();
will(returnValue(friendAddress));
allowing(friendAddress).getFullId();
will(returnValue("403"));
// Assertions
exactly(2).of(networkManager).acceptedIncomingConnection();
will(returnValue(true));
allowing(friendAddressResolver).getPresence(friendAddress);
will(returnValue(friendPresence));
allowing(friendPresence).getTransport(ConnectBackRequestFeature.class);
will(returnValue(connectBackTransport));
one(connectBackTransport).sendFeature(friendPresence, connectBackRequest);
will(throwException(new FriendException("error sending")));
one(connectBackTransport).sendFeature(friendPresence, connectBackRequest);
exactly(1).of(pushDownloadManager).connect(fwAddress, observer);
exactly(1).of(backgroundExecutor).schedule(with(any(Runnable.class)),
with(any(Integer.class)), with((any(TimeUnit.class))));
}});
// Connect
connector.connect(address, observer);
connector.connect(address, observer);
context.assertIsSatisfied();
}
}