package com.limegroup.gnutella;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import junit.framework.Test;
import org.limewire.core.api.browse.BrowseListener;
import org.limewire.core.api.friend.feature.features.AddressFeature;
import org.limewire.core.api.search.SearchResult;
import org.limewire.io.GUID;
import org.limewire.io.IpPortImpl;
import org.limewire.io.IpPortSet;
import org.limewire.util.Base32;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.google.inject.Stage;
import com.limegroup.gnutella.helpers.UrnHelper;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.PushRequest;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryReplyFactory;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
import com.limegroup.gnutella.stubs.NetworkManagerStub;
/**
* Checks whether (multi)leaves avoid forwarding messages to ultrapeers, do
* redirects properly, etc. The test includes a leaf attached to 3
* ultrapeers.
*/
public class ClientSideBrowseHostTest extends ClientSideTestCase {
private MyActivityCallback callback;
private NetworkManagerStub networkManagerStub;
private SearchServices searchServices;
private ResponseFactory responseFactory;
private QueryReplyFactory queryReplyFactory;
public ClientSideBrowseHostTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(ClientSideBrowseHostTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
///////////////////////// Actual Tests ////////////////////////////
// Tests the following behaviors:
// ------------------------------
// 1. that the client makes a correct direct connection if possible
// 2. that the client makes a correct push proxy connection if necessary
// 3. if all else fails the client sends a PushRequest
@Override
protected void setUp() throws Exception {
networkManagerStub = new NetworkManagerStub();
networkManagerStub.setAcceptedIncomingConnection(true);
networkManagerStub.setPort(SERVER_PORT);
networkManagerStub.setExternalAddress(new byte[] { (byte)129, 1, 4, 10 });
Injector injector = LimeTestUtils.createInjector(Stage.PRODUCTION, MyActivityCallback.class,
new LimeTestUtils.NetworkManagerStubModule(networkManagerStub));
super.setUp(injector);
callback = (MyActivityCallback) injector.getInstance(ActivityCallback.class);
searchServices = injector.getInstance(SearchServices.class);
responseFactory = injector.getInstance(ResponseFactory.class);
queryReplyFactory = injector.getInstance(QueryReplyFactory.class);
}
public void testHTTPRequest() throws Exception {
BlockingConnectionUtils.drain(testUP[0]);
// some setup
final byte[] clientGUID = GUID.makeGuid();
// construct and send a query
byte[] guid = GUID.makeGuid();
searchServices.query(guid, "boalt.org");
// the testUP[0] should get it
Message m = null;
do {
m = testUP[0].receive(TIMEOUT);
} while (!(m instanceof QueryRequest)) ;
// set up a server socket
ServerSocket ss = new ServerSocket(7000);
try {
ss.setReuseAddress(true);
ss.setSoTimeout(TIMEOUT);
// send a reply
Response[] res = new Response[1];
res[0] = responseFactory.createResponse(10, 10, "boalt.org", UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(m.getGUID(), (byte) 1, 7000,
InetAddress.getLocalHost().getAddress(), 0, res, clientGUID, new byte[0], false, false,
true, true, false, false, null);
testUP[0].send(m);
testUP[0].flush();
// wait a while for Leaf to process result
assertNotNull(callback.getRFD());
// tell the leaf to browse host the file, should result in direct HTTP
// request
searchServices.doAsynchronousBrowseHost(new MockFriendPresence(new MockFriend(), new AddressFeature(callback.getRFD().getAddress())), new GUID(), new BrowseListener() {
public void handleBrowseResult(SearchResult searchResult) {
//To change body of implemented methods use File | Settings | File Templates.
}
public void browseFinished(boolean success) {
//To change body of implemented methods use File | Settings | File Templates.
}
});
// wait for the incoming HTTP request
Socket httpSock = ss.accept();
try {
assertIsBrowse(httpSock, 7000);
} finally {
httpSock.close();
}
try {
do {
m = testUP[0].receive(TIMEOUT);
assertTrue(!(m instanceof PushRequest));
} while (true) ;
}
catch (InterruptedIOException expected) {}
} finally {
// awesome - everything checks out!
ss.close();
}
}
public void testPushProxyRequest() throws Exception {
// wait for connections to process any messages
Thread.sleep(6000);
BlockingConnectionUtils.drain(testUP[0]);
// some setup
final byte[] clientGUID = GUID.makeGuid();
// construct and send a query
byte[] guid = GUID.makeGuid();
searchServices.query(guid, "nyu.edu");
// the testUP[0] should get it
Message m = null;
do {
m = testUP[0].receive(TIMEOUT);
} while (!(m instanceof QueryRequest)) ;
// set up a server socket to wait for proxy request
ServerSocket ss = new ServerSocket(7000);
try {
ss.setReuseAddress(true);
ss.setSoTimeout(TIMEOUT*4);
// send a reply with some PushProxy info
final IpPortSet proxies = new IpPortSet();
proxies.add(new IpPortImpl("127.0.0.1", 7000));
Response[] res = new Response[1];
res[0] = responseFactory.createResponse(10, 10, "nyu.edu", UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(m.getGUID(), (byte) 1, 6999,
InetAddress.getLocalHost().getAddress(), 0, res, clientGUID, new byte[0], true, false,
true, true, false, false, proxies);
testUP[0].send(m);
testUP[0].flush();
// wait a while for Leaf to process result
assertNotNull(callback.getRFD());
// tell the leaf to browse host the file, should result in PushProxy
// request
searchServices.doAsynchronousBrowseHost(new MockFriendPresence(new MockFriend(), new AddressFeature(callback.getRFD().getAddress())), new GUID(), new BrowseListener() {
public void handleBrowseResult(SearchResult searchResult) {
//To change body of implemented methods use File | Settings | File Templates.
}
public void browseFinished(boolean success) {
//To change body of implemented methods use File | Settings | File Templates.
}
});
// wait for the incoming PushProxy request
// increase the timeout since we send udp pushes first
ss.setSoTimeout(7000);
Socket httpSock = ss.accept();
try {
BufferedWriter sockWriter =
new BufferedWriter(new
OutputStreamWriter(httpSock.getOutputStream()));
sockWriter.write("HTTP/1.1 202 OK\r\n");
sockWriter.flush();
// start reading and confirming the HTTP request
String currLine = null;
BufferedReader reader =
new BufferedReader(new
InputStreamReader(httpSock.getInputStream()));
// confirm a GET/HEAD pushproxy request
currLine = reader.readLine();
assertTrue(currLine.startsWith("GET /gnutella/push-proxy") ||
currLine.startsWith("HEAD /gnutella/push-proxy"));
// make sure it sends the correct client GUID
int beginIndex = currLine.indexOf("ID=") + 3;
String guidString = currLine.substring(beginIndex, beginIndex+26);
GUID guidFromBackend = new GUID(clientGUID);
GUID guidFromNetwork = new GUID(Base32.decode(guidString));
assertEquals(guidFromNetwork, guidFromBackend);
// make sure the node sends the correct X-Node
currLine = reader.readLine();
assertTrue(currLine.startsWith("X-Node:"));
StringTokenizer st = new StringTokenizer(currLine, ":");
assertEquals(st.nextToken(), "X-Node");
InetAddress addr = InetAddress.getByName(st.nextToken().trim());
Arrays.equals(addr.getAddress(), networkManagerStub.getAddress());
assertEquals(SERVER_PORT, Integer.parseInt(st.nextToken()));
// now we need to GIV
Socket push = new Socket(InetAddress.getLocalHost(), SERVER_PORT);
try {
BufferedWriter writer =
new BufferedWriter(new
OutputStreamWriter(push.getOutputStream()));
writer.write("GIV 0:" + new GUID(clientGUID).toHexString() + "/\r\n");
writer.write("\r\n");
writer.flush();
assertIsBrowse(push, push.getLocalPort());
} finally {
push.close();
}
} finally {
httpSock.close();
}
try {
do {
m = testUP[0].receive(TIMEOUT);
assertNotInstanceof(m.toString(), PushRequest.class, m);
} while (true) ;
}
catch (InterruptedIOException expected) {}
} finally {
ss.close();
}
}
public void testSendsPushRequest() throws Exception {
BlockingConnectionUtils.drain(testUP[0]);
// some setup
final byte[] clientGUID = GUID.makeGuid();
// construct and send a query
byte[] guid = GUID.makeGuid();
searchServices.query(guid, "anita");
// the testUP[0] should get it
Message m = null;
do {
m = testUP[0].receive(TIMEOUT);
} while (!(m instanceof QueryRequest)) ;
// send a reply with some BAD PushProxy info
final IpPortSet proxies = new IpPortSet(new IpPortImpl("127.0.0.1", 7001));
Response[] res = new Response[] { responseFactory.createResponse(10, 10, "anita", UrnHelper.SHA1) };
m = queryReplyFactory.createQueryReply(m.getGUID(), (byte) 1, 7000,
InetAddress.getLocalHost().getAddress(), 0, res, clientGUID, new byte[0], true, false, true,
true, false, false, proxies);
testUP[0].send(m);
testUP[0].flush();
// wait a while for Leaf to process result
assertNotNull(callback.getRFD());
// tell the leaf to browse host the file,
searchServices.doAsynchronousBrowseHost(new MockFriendPresence(new MockFriend(), new AddressFeature(callback.getRFD().getAddress())), new GUID(), new BrowseListener() {
public void handleBrowseResult(SearchResult searchResult) {
//To change body of implemented methods use File | Settings | File Templates.
}
public void browseFinished(boolean success) {
//To change body of implemented methods use File | Settings | File Templates.
}
});
// nothing works for the guy, we should get a PushRequest
do {
m = testUP[0].receive(TIMEOUT*30);
} while (!(m instanceof PushRequest));
// awesome - everything checks out!
}
private void assertIsBrowse(Socket httpSock, int port) throws IOException {
// start reading and confirming the HTTP request
String currLine = null;
BufferedReader reader = new BufferedReader(new InputStreamReader(
httpSock.getInputStream()));
// confirm a GET/HEAD push proxy request
currLine = reader.readLine();
assertEquals("GET / HTTP/1.1", currLine);
// make sure the node sends the correct Host val
currLine = reader.readLine();
assertTrue(currLine.startsWith("Host:"));
StringTokenizer st = new StringTokenizer(currLine, ":");
assertEquals(st.nextToken(), "Host");
// this assertion fails when localhost is bound to multiple IP addresses
// since the client might connect to a different address than the server
// socket is listening on
InetAddress.getByName(st.nextToken().trim());
// assertEquals(InetAddress.getByName(st.nextToken().trim()), addr);
assertEquals(port, Integer.parseInt(st.nextToken()));
// send back a 200 and make sure no PushRequest is sent via the normal
// way
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
httpSock.getOutputStream()));
writer.write("HTTP/1.1 200 OK\r\n");
writer.flush();
writer.write("\r\n");
writer.flush();
// TODO: should i send some Query Hits? Might be a good test.
}
@Override
public int getNumberOfPeers() {
return 1;
}
@Singleton
private static class MyActivityCallback extends ActivityCallbackStub {
private volatile RemoteFileDesc remoteFileDesc;
private final CountDownLatch latch = new CountDownLatch(1);
public RemoteFileDesc getRFD() throws InterruptedException {
latch.await(5, TimeUnit.SECONDS);
return remoteFileDesc;
}
@Override
public void handleQueryResult(RemoteFileDesc rfd,
QueryReply queryReply,
Set locs) {
remoteFileDesc = rfd;
latch.countDown();
}
}
}