package com.limegroup.gnutella.uploader; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ScheduledExecutorService; import junit.framework.Test; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.ProtocolException; import org.apache.http.message.BasicHttpRequest; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicLineParser; import org.apache.http.protocol.HTTP; import org.limewire.core.settings.ConnectionSettings; import org.limewire.core.settings.FilterSettings; import org.limewire.core.settings.LibrarySettings; import org.limewire.core.settings.NetworkSettings; import org.limewire.core.settings.SharingSettings; import org.limewire.core.settings.UltrapeerSettings; import org.limewire.core.settings.UploadSettings; import org.limewire.gnutella.tests.LimeTestCase; import org.limewire.gnutella.tests.LimeTestUtils; import org.limewire.io.Connectable; import org.limewire.io.ConnectableImpl; import org.limewire.io.GUID; import org.limewire.io.NetworkInstanceUtils; import org.limewire.net.ConnectionDispatcher; import org.limewire.net.SocketsManager; import org.limewire.nio.ByteBufferCache; import org.limewire.nio.NIOTestUtils; import org.limewire.util.TestUtils; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.name.Named; import com.limegroup.gnutella.Acceptor; import com.limegroup.gnutella.ActivityCallback; import com.limegroup.gnutella.ApplicationServices; import com.limegroup.gnutella.ConnectionManager; import com.limegroup.gnutella.ConnectionManagerImpl; import com.limegroup.gnutella.ConnectionServices; import com.limegroup.gnutella.HostCatcher; import com.limegroup.gnutella.LifecycleManager; import com.limegroup.gnutella.NetworkManager; import com.limegroup.gnutella.NetworkManagerImpl; import com.limegroup.gnutella.NodeAssigner; import com.limegroup.gnutella.QueryUnicaster; import com.limegroup.gnutella.UDPService; import com.limegroup.gnutella.connection.BlockingConnection; import com.limegroup.gnutella.connection.BlockingConnectionFactory; import com.limegroup.gnutella.connection.ConnectionCheckerManager; import com.limegroup.gnutella.connection.RoutedConnectionFactory; import com.limegroup.gnutella.dht.DHTManager; import com.limegroup.gnutella.filters.IPFilter; import com.limegroup.gnutella.handshaking.HandshakeResponder; import com.limegroup.gnutella.handshaking.HandshakeResponse; import com.limegroup.gnutella.handshaking.HeadersFactory; import com.limegroup.gnutella.library.FileCollection; import com.limegroup.gnutella.library.FileDesc; import com.limegroup.gnutella.library.FileManagerTestUtils; import com.limegroup.gnutella.library.GnutellaFiles; import com.limegroup.gnutella.library.Library; import com.limegroup.gnutella.messages.BadPacketException; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.messages.PingRequestFactory; import com.limegroup.gnutella.messages.PushRequest; import com.limegroup.gnutella.messages.PushRequestImpl; import com.limegroup.gnutella.messages.QueryReply; import com.limegroup.gnutella.messages.QueryRequest; import com.limegroup.gnutella.messages.QueryRequestFactory; import com.limegroup.gnutella.messages.vendor.CapabilitiesVMFactory; import com.limegroup.gnutella.simpp.SimppManager; import com.limegroup.gnutella.statistics.OutOfBandStatistics; //ITEST public class PushUploadTest extends LimeTestCase { private final int PORT = 6668; /** Our listening port for pushes. */ private final int PUSH_PORT = 6671; private String testDirName = "com/limegroup/gnutella/uploader/data"; private String fileName = "alphabet test file#2.txt"; private String url; private byte[] guid; private Socket socket; /** The file contents. */ private final String alphabet = "abcdefghijklmnopqrstuvwxyz"; @Inject private Library library; private BufferedReader in; private BufferedWriter out; @Inject private LifecycleManager lifeCycleManager; private MyNetworkManager networkManager; private MyConnectionManager connectionManager; @Inject private Injector injector; @Inject @GnutellaFiles FileCollection gnutellaFileCollection; public PushUploadTest(String name) { super(name); } public static Test suite() { return buildTestSuite(PushUploadTest.class); } private void doSettings() throws Exception { LibrarySettings.VERSION.set(LibrarySettings.LibraryVersion.FIVE_0_0.name()); SharingSettings.ADD_ALTERNATE_FOR_SELF.setValue(false); FilterSettings.BLACK_LISTED_IP_ADDRESSES .set(new String[] { "*.*.*.*" }); FilterSettings.WHITE_LISTED_IP_ADDRESSES.set(new String[] { "127.*.*.*", InetAddress.getLocalHost().getHostAddress() }); NetworkSettings.PORT.setValue(PORT); UploadSettings.HARD_MAX_UPLOADS.setValue(10); UploadSettings.UPLOADS_PER_PERSON.setValue(10); UploadSettings.MAX_PUSHES_PER_HOST.setValue(9999); FilterSettings.FILTER_DUPLICATES.setValue(false); ConnectionSettings.NUM_CONNECTIONS.setValue(8); ConnectionSettings.CONNECT_ON_STARTUP.setValue(true); ConnectionSettings.LOCAL_IS_PRIVATE.setValue(false); ConnectionSettings.EVER_ACCEPTED_INCOMING.setValue(true); ConnectionSettings.ALLOW_WHILE_DISCONNECTED.setValue(true); UltrapeerSettings.FORCE_ULTRAPEER_MODE.setValue(true); UltrapeerSettings.EVER_ULTRAPEER_CAPABLE.setValue(true); } @Override protected void setUp() throws Exception { doSettings(); injector = LimeTestUtils.createInjector(new AbstractModule() { @Override protected void configure() { bind(NetworkManager.class).to(MyNetworkManager.class); bind(ConnectionManager.class).to(MyConnectionManager.class); } }, LimeTestUtils.createModule(this)); // start services FileManagerTestUtils.waitForLoad(library, 1000); File testDir = TestUtils.getResourceFile(testDirName); FileDesc fd = gnutellaFileCollection.add(new File(testDir, fileName)).get(); url = LimeTestUtils.getRelativeRequest(fd.getSHA1Urn()); lifeCycleManager.start(); networkManager = (MyNetworkManager) injector.getInstance(NetworkManager.class); connectionManager = (MyConnectionManager) injector.getInstance(ConnectionManager.class); } @Override public void tearDown() throws Exception { closeConnection(); if (lifeCycleManager != null) { lifeCycleManager.shutdown(); } NIOTestUtils.waitForNIO(); } public void testDownloadHTTP10() throws Exception { establishPushConnection(); HttpRequest request = new BasicHttpRequest("GET", url, HttpVersion.HTTP_1_0); HttpResponse response = sendRequest(request); assertEquals(200, response.getStatusLine().getStatusCode()); String body = readBody(response); assertEquals(alphabet, body); } public void testDownloadHTTP10PushRange() throws Exception { establishPushConnection(); HttpRequest request = new BasicHttpRequest("GET", url, HttpVersion.HTTP_1_0); request.addHeader("Range", "bytes=2-5"); HttpResponse response = sendRequest(request); assertEquals(206, response.getStatusLine().getStatusCode()); String body = readBody(response); assertEquals("cdef", body); } public void testDownloadHTTP11() throws Exception { establishPushConnection(); HttpRequest request = new BasicHttpRequest("GET", url, HttpVersion.HTTP_1_1); HttpResponse response = sendRequest(request); assertEquals(200, response.getStatusLine().getStatusCode()); String body = readBody(response); assertEquals(alphabet, body); assertFalse(socket.isClosed()); response = sendRequest(request); assertEquals(200, response.getStatusLine().getStatusCode()); body = readBody(response); assertEquals(alphabet, body); } public void testDownloadHeadHTTP11() throws Exception { establishPushConnection(); HttpRequest request = new BasicHttpRequest("HEAD", url, HttpVersion.HTTP_1_1); HttpResponse response = sendRequest(request); assertEquals(200, response.getStatusLine().getStatusCode()); assertFalse(socket.isClosed()); request.addHeader("Connection", "close"); response = sendRequest(request); assertEquals(200, response.getStatusLine().getStatusCode()); assertEquals(-1, in.read()); } public void testHTTP11PushRange() throws Exception { establishPushConnection(); HttpRequest request = new BasicHttpRequest("GET", url, HttpVersion.HTTP_1_1); request.addHeader("Range", "bytes=2-5"); HttpResponse response = sendRequest(request); assertEquals(206, response.getStatusLine().getStatusCode()); String body = readBody(response); assertEquals("cdef", body); response = sendRequest(request); assertEquals(206, response.getStatusLine().getStatusCode()); body = readBody(response); assertEquals("cdef", body); } public void testHTTP11PipeliningDownloadPush() throws Exception { establishPushConnection(); HttpRequest request = new BasicHttpRequest("GET", url, HttpVersion.HTTP_1_1); writeRequest(out, request); writeRequest(out, request); HttpResponse response = readResponse(in); assertEquals(200, response.getStatusLine().getStatusCode()); String body = readBody(response); assertEquals(alphabet, body); assertFalse(socket.isClosed()); response = readResponse(in); assertEquals(200, response.getStatusLine().getStatusCode()); body = readBody(response); assertEquals(alphabet, body); assertFalse(socket.isClosed()); writeRequest(out, request); response = readResponse(in); assertEquals(200, response.getStatusLine().getStatusCode()); body = readBody(response); assertEquals(alphabet, body); assertFalse(socket.isClosed()); } /** * Tests that the node sends a proper proxies header. */ public void testForPushProxyHeaderWithoutProxy() throws Exception { // try when we are not firewalled networkManager.acceptedIncomingConnection = true; establishConnection(); HttpRequest request = new BasicHttpRequest("GET", url, HttpVersion.HTTP_1_1); HttpResponse response = sendRequest(request); UploadTestUtils.assertNotHasHeader(response, "X-Push-Proxy: 1.2.3.4:5"); // now try with an empty set of proxies establishPushConnection(); networkManager.acceptedIncomingConnection = false; response = sendRequest(request); UploadTestUtils.assertNotHasHeader(response, "X-Push-Proxy: 1.2.3.4:5"); } public void testForPushProxyHeaderWithProxy() throws Exception { establishPushConnection(); // now try with some proxies Connectable ppi = new ConnectableImpl("1.2.3.4", 5, false); connectionManager.proxies.add(ppi); networkManager.acceptedIncomingConnection = false; HttpRequest request = new BasicHttpRequest("GET", url, HttpVersion.HTTP_1_1); HttpResponse response = sendRequest(request); UploadTestUtils.assertHasHeader(response, "X-Push-Proxy: 1.2.3.4:5"); } /** * Tests the scenario where we receive the same push request message more * than once. */ public void testDuplicatePushes() throws Exception { BlockingConnection connection = injector.getInstance(BlockingConnectionFactory.class).createConnection("localhost", PORT); try { connection.initialize(injector.getInstance(HeadersFactory.class).createUltrapeerHeaders(null), new EmptyResponder(), 1000); QueryRequest query = injector.getInstance(QueryRequestFactory.class).createQuery("txt", (byte) 3); connection.send(query); connection.flush(); QueryReply reply = null; for (int i = 0; i < 10; i++) { Message m = connection.receive(2000); if (m instanceof QueryReply) { reply = (QueryReply) m; break; } } if (reply == null) throw new IOException("didn't get query reply in time"); PushRequest push = new PushRequestImpl(GUID.makeGuid(), (byte) 3, reply .getClientGUID(), 0, new byte[] { (byte) 127, (byte) 0, (byte) 0, (byte) 1 }, PUSH_PORT); // Create listening socket, then send the push a few times ServerSocket serverSocket = new ServerSocket(PUSH_PORT); try { serverSocket.setSoTimeout(1000); connection.send(push); connection.send(push); connection.send(push); connection.flush(); assertNotNull(serverSocket.accept()); // get one. // the last two shouldn't be gotten. try { serverSocket.accept(); fail("Node replied to duplicate push request"); } catch (IOException expected) { } try { serverSocket.accept(); fail("Node replied to duplicate push request"); } catch (IOException expected) { } } finally { serverSocket.close(); } } finally { connection.close(); } } /** * Does a push and gets a socket from the incoming connection. */ private Socket getSocketFromPush() throws IOException, BadPacketException { BlockingConnection connection = injector.getInstance(BlockingConnectionFactory.class).createConnection("localhost", PORT); try { connection.initialize(injector.getInstance(HeadersFactory.class).createUltrapeerHeaders(null), new EmptyResponder(), 1000); // send query QueryRequest query = injector.getInstance(QueryRequestFactory.class).createQuery("txt", (byte) 3); connection.send(query); connection.flush(); // look for reply QueryReply reply = null; for (int i = 0; i < 10; i++) { Message m = connection.receive(2000); if (m instanceof QueryReply) { reply = (QueryReply) m; break; } } if (reply == null) { throw new IOException("Did not get query reply in time"); } // create listening socket and wait for push connect ServerSocket serverSocket = new ServerSocket(PUSH_PORT); // send push guid = reply.getClientGUID(); PushRequest push = new PushRequestImpl(GUID.makeGuid(), (byte) 3, guid, 0, new byte[] { (byte) 127, (byte) 0, (byte) 0, (byte) 1 }, PUSH_PORT); connection.send(push); connection.flush(); try { serverSocket.setSoTimeout(1000); return serverSocket.accept(); } finally { serverSocket.close(); } } finally { connection.close(); } } /** * Does a push download. */ private void establishPushConnection() throws BadPacketException, IOException { closeConnection(); socket = getSocketFromPush(); in = new BufferedReader(new InputStreamReader(socket.getInputStream(), HTTP.DEFAULT_PROTOCOL_CHARSET)); out = new BufferedWriter(new OutputStreamWriter(socket .getOutputStream(), HTTP.DEFAULT_PROTOCOL_CHARSET)); assertEquals("GIV 0:" + new GUID(guid).toString() + "/file", in.readLine()); assertEquals("", in.readLine()); } private void closeConnection() throws IOException { if (socket != null) { // close connection socket.close(); socket = null; } } private void establishConnection() throws IOException { closeConnection(); socket = new Socket("localhost", PORT); in = new BufferedReader(new InputStreamReader(socket.getInputStream(), HTTP.DEFAULT_PROTOCOL_CHARSET)); out = new BufferedWriter(new OutputStreamWriter(socket .getOutputStream(), HTTP.DEFAULT_PROTOCOL_CHARSET)); } private HttpResponse sendRequest(HttpRequest request) throws Exception { writeRequest(out, request); return readResponse(in); } private void writeRequest(BufferedWriter out, HttpRequest request) throws IOException { out.write(request.getRequestLine().toString()); out.write("\r\n"); for (org.apache.http.Header header : request.getAllHeaders()) { out.write(header.toString()); out.write("\r\n"); } out.write("\r\n"); out.flush(); } private HttpResponse readResponse(BufferedReader in) throws IOException, ProtocolException, UnsupportedEncodingException { // read status line String line = in.readLine(); assertNotNull("Unexpected end of stream", line); BasicHttpResponse response = new BasicHttpResponse(BasicLineParser.parseStatusLine(line, null)); // read headers while ((line = in.readLine()) != null) { if ("".equals(line)) { break; } int i = line.indexOf(":"); assertNotEquals("Malformed header: " + line, -1, i); String name = line.substring(0, i); String value = line.substring(i + 2); response.addHeader(name, value); } assertNotNull("Unexpected end of stream while reading headers", line); return response; } private String readBody(HttpResponse response) throws IOException { int contentLength = -1; for (org.apache.http.Header header : response.getAllHeaders()) { if ("Content-Length".equals(header.getName())) { contentLength = Integer.parseInt(header.getValue()); } } // read body StringBuilder body = new StringBuilder(); while (contentLength == -1 || body.length() < contentLength) { int c = in.read(); if (c == -1) { fail("Unexpected end of stream while reading body (read " + body.length() + ", expected " + contentLength + "): " + body.toString()); } body.append((char) c); } return body.toString(); } private static class EmptyResponder implements HandshakeResponder { public HandshakeResponse respond(HandshakeResponse response, boolean outgoing) { return HandshakeResponse.createResponse(new Properties()); } public void setLocalePreferencing(boolean b) { } } @Singleton private static class MyNetworkManager extends NetworkManagerImpl { private boolean acceptedIncomingConnection = true; @Inject public MyNetworkManager(Provider<UDPService> udpService, Provider<Acceptor> acceptor, Provider<DHTManager> dhtManager, Provider<ConnectionManager> connectionManager, Provider<ActivityCallback> activityCallback, OutOfBandStatistics outOfBandStatistics, NetworkInstanceUtils networkInstanceUtils, Provider<CapabilitiesVMFactory> capabilitiesVMFactory, Provider<ByteBufferCache> bbCache, ApplicationServices applicationServices) { super(udpService, acceptor, dhtManager, connectionManager, outOfBandStatistics, networkInstanceUtils, capabilitiesVMFactory, bbCache, applicationServices); } @Override public boolean acceptedIncomingConnection() { return acceptedIncomingConnection; } } @Singleton private static class MyConnectionManager extends ConnectionManagerImpl { private Set<Connectable> proxies = new TreeSet<Connectable>(); @Inject public MyConnectionManager(NetworkManager networkManager, Provider<HostCatcher> hostCatcher, @Named("global") Provider<ConnectionDispatcher> connectionDispatcher, @Named("backgroundExecutor") ScheduledExecutorService backgroundExecutor, Provider<SimppManager> simppManager, CapabilitiesVMFactory capabilitiesVMFactory, RoutedConnectionFactory managedConnectionFactory, Provider<QueryUnicaster> queryUnicaster, SocketsManager socketsManager, ConnectionServices connectionServices, Provider<NodeAssigner> nodeAssigner, Provider<IPFilter> ipFilter, ConnectionCheckerManager connectionCheckerManager, PingRequestFactory pingRequestFactory, NetworkInstanceUtils networkInstanceUtils) { super(networkManager, hostCatcher, connectionDispatcher, backgroundExecutor, simppManager, capabilitiesVMFactory, managedConnectionFactory, queryUnicaster, socketsManager, connectionServices, nodeAssigner, ipFilter, connectionCheckerManager, pingRequestFactory, networkInstanceUtils); } @Override public Set<Connectable> getPushProxies() { return proxies ; } }; }