package com.limegroup.gnutella.downloader;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import junit.framework.Test;
import org.limewire.io.Connectable;
import org.limewire.io.ConnectableImpl;
import org.limewire.io.GUID;
import org.limewire.io.IOUtils;
import org.limewire.io.NetworkInstanceUtils;
import org.limewire.io.SimpleNetworkInstanceUtils;
import org.limewire.net.BlockingConnectObserver;
import org.limewire.net.address.FirewalledAddress;
import org.mortbay.http.HttpContext;
import org.mortbay.http.HttpException;
import org.mortbay.http.HttpRequest;
import org.mortbay.http.HttpResponse;
import org.mortbay.http.HttpServer;
import org.mortbay.http.SocketListener;
import org.mortbay.http.handler.AbstractHttpHandler;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.google.inject.Stage;
import com.limegroup.gnutella.Acceptor;
import com.limegroup.gnutella.ConnectionServices;
import com.limegroup.gnutella.LifecycleManager;
import com.limegroup.gnutella.LimeTestUtils;
import com.limegroup.gnutella.NetworkManager;
import com.limegroup.gnutella.stubs.AcceptorStub;
import com.limegroup.gnutella.util.LimeTestCase;
public class PushDownloadManagerTest extends LimeTestCase {
private PushDownloadManager pushDownloadManager;
private PushedSocketHandlerStub browser;
private PushedSocketHandlerStub downloader;
private Injector injector;
public PushDownloadManagerTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(PushDownloadManagerTest.class);
}
@Override
protected void setUp() throws Exception {
injector = LimeTestUtils.createInjector(Stage.PRODUCTION, new AbstractModule() {
@Override
protected void configure() {
bind(DownloaderStub.class).asEagerSingleton();
bind(BrowserStub.class).asEagerSingleton();
bind(NetworkInstanceUtils.class).toInstance(new SimpleNetworkInstanceUtils(false));
bind(Acceptor.class).to(AcceptorStub.class);
}
});
pushDownloadManager = injector.getInstance(PushDownloadManager.class);
browser = injector.getInstance(DownloaderStub.class);
downloader = injector.getInstance(BrowserStub.class);
}
/**
* Integration test to ensure that pushed connecting works
*/
public void testConnect() throws Exception {
LifecycleManager lifecycleManager = injector.getInstance(LifecycleManager.class);
NetworkManager networkManager = injector.getInstance(NetworkManager.class);
ConnectionServices connectionServices = injector.getInstance(ConnectionServices.class);
Acceptor acceptor = injector.getInstance(Acceptor.class);
// need to have valid address and accepted incoming to support non-fwt push connects
acceptor.setAddress(InetAddress.getByAddress(new byte[] { (byte) 192, (byte) 168, 0, 1 }));
((AcceptorStub)acceptor).setAcceptedIncoming(true);
lifecycleManager.start();
connectionServices.connect();
HTTPPushProxyServer server = null;
try {
server = new HTTPPushProxyServer(9999);
server.start();
BlockingConnectObserver observer = new BlockingConnectObserver();
Set<Connectable> proxies = new TreeSet<Connectable>();
proxies.add(new ConnectableImpl("localhost", 9999, false));
networkManager.newPushProxies(proxies);
GUID guid = new GUID();
Connectable hostAddress = new ConnectableImpl(new ConnectableImpl("localhost", 1111, false));
FirewalledAddress address = new FirewalledAddress(ConnectableImpl.INVALID_CONNECTABLE, hostAddress, guid, proxies, 0);
assertTrue(pushDownloadManager.canConnect(address));
pushDownloadManager.connect(address, observer);
// long wait, since we wait for the tcp push as failover after the udp push
assertTrue(server.latch.await(10, TimeUnit.SECONDS));
GiveWritingSocket socket = new GiveWritingSocket(guid, networkManager.getPort());
Socket receivedSocket = observer.getSocket(5, TimeUnit.SECONDS);
assertEquals(socket.socket.getLocalSocketAddress(), receivedSocket.getRemoteSocketAddress());
IOUtils.close(socket);
IOUtils.close(receivedSocket);
} finally {
IOUtils.close(server);
connectionServices.disconnect();
lifecycleManager.shutdown();
}
}
public void testHandleGIVDownload() {
byte [] clientGUIDBytes = GUID.makeGuid();
downloader.setClientGUID(clientGUIDBytes);
Socket socket = new Socket();
assertFalse(socket.isClosed());
PushDownloadManager.GIVLine givLine = new PushDownloadManager.GIVLine("foo", 1, clientGUIDBytes);
pushDownloadManager.handleGIV(socket, givLine);
assertTrue(downloader.accepted());
assertFalse(browser.accepted());
assertFalse(socket.isClosed());
}
public void testHandleGIVBrowse() {
byte [] clientGUIDBytes = GUID.makeGuid();
browser.setClientGUID(clientGUIDBytes);
Socket socket = new Socket();
assertFalse(socket.isClosed());
PushDownloadManager.GIVLine givLine = new PushDownloadManager.GIVLine("foo", 1, clientGUIDBytes);
pushDownloadManager.handleGIV(socket, givLine);
assertFalse(downloader.accepted());
assertTrue(browser.accepted());
assertFalse(socket.isClosed());
}
public void testHandleGIVReject() {
byte [] clientGUIDBytes = GUID.makeGuid();
Socket socket = new Socket();
assertFalse(socket.isClosed());
PushDownloadManager.GIVLine givLine = new PushDownloadManager.GIVLine("foo", 1, clientGUIDBytes);
pushDownloadManager.handleGIV(socket, givLine);
assertFalse(downloader.accepted());
assertFalse(browser.accepted());
assertTrue(socket.isClosed());
}
public static class PushedSocketHandlerStub implements PushedSocketHandler {
byte [] clientGUID;
boolean acceptedIncomingConnection = false;
@Inject
public PushedSocketHandlerStub() {}
@Inject
public void register(PushedSocketHandlerRegistry registry) {
registry.register(this);
}
boolean accepted() {
return acceptedIncomingConnection;
}
void setClientGUID(byte [] clientGUID) {
this.clientGUID = clientGUID;
}
public boolean acceptPushedSocket(String file, int index, byte[] clientGUID, Socket socket) {
if(Arrays.equals(this.clientGUID, clientGUID)) {
acceptedIncomingConnection = true;
return true;
} else {
return false;
}
}
}
@Singleton
public static class DownloaderStub extends PushedSocketHandlerStub{}
@Singleton
public static class BrowserStub extends PushedSocketHandlerStub{}
private static class HTTPPushProxyServer implements Closeable {
private HttpServer server = new HttpServer();
CountDownLatch latch = new CountDownLatch(1);
public HTTPPushProxyServer(int port) {
SocketListener listener = new SocketListener();
listener.setPort(port);
listener.setMinThreads(1);
listener.setMaxThreads(5);
server.addListener(listener);
HttpContext context = server.addContext("/");
context.addHandler(new AbstractHttpHandler() {
@Override
public void handle(String target, String hm, HttpRequest request, HttpResponse response)
throws HttpException, IOException {
response.setStatus(HttpResponse.__202_Accepted);
response.commit();
latch.countDown();
}
});
}
void start() throws Exception {
server.start();
}
public void close() throws IOException {
try {
server.stop();
server.join();
} catch (InterruptedException e) {
throw new IOException(e);
}
}
}
private static class GiveWritingSocket implements Closeable {
private Socket socket;
public GiveWritingSocket(GUID guid, int port) throws IOException {
socket = new Socket("localhost", port);
socket.setSoTimeout(30 * 1000);
OutputStream out = socket.getOutputStream();
out.write(("GIV 0:" + guid + "/file\n\n").getBytes());
out.flush();
}
@Override
public void close() throws IOException {
socket.close();
}
}
}