package com.limegroup.gnutella;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import junit.framework.Test;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.PingReply;
import com.limegroup.gnutella.messages.PingRequest;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.messages.vendor.LimeACKVendorMessage;
import com.limegroup.gnutella.messages.vendor.MessagesSupportedVendorMessage;
import com.limegroup.gnutella.messages.vendor.QueryStatusResponse;
import com.limegroup.gnutella.messages.vendor.ReplyNumberVendorMessage;
import com.limegroup.gnutella.messages.vendor.UDPConnectBackVendorMessage;
import com.limegroup.gnutella.routing.QueryRouteTable;
import com.limegroup.gnutella.routing.RouteTableMessage;
import com.limegroup.gnutella.search.SearchResultHandler;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
import com.limegroup.gnutella.util.PrivilegedAccessor;
/**
* Tests that an Ultrapeer correctly proxies for a Leaf.
*
* ULTRAPEER[0] ---- CENTRAL TEST ULTRAPEER ---- ULTRAPEER[1]
* |
* |
* |
* LEAF[0]
*
*/
public final class ServerSideOOBProxyTest extends ServerSideTestCase {
private final int MAX_RESULTS = SearchResultHandler.MAX_RESULTS;
private static final long EXPIRE_TIME = 20 * 1000;
protected static int TIMEOUT = 2000;
private static final Log LOG = LogFactory.getLog(ServerSideOOBProxyTest.class);
/**
* Ultrapeer 1 UDP connection.
*/
private static DatagramSocket UDP_ACCESS;
public ServerSideOOBProxyTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(ServerSideOOBProxyTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
public static Integer numUPs() {
return new Integer(2);
}
public static Integer numLeaves() {
return new Integer(2);
}
public static ActivityCallback getActivityCallback() {
return new ActivityCallbackStub();
}
public static void setSettings() throws Exception {
// we want to test the expirer so make the expire period small
PrivilegedAccessor.setValue(ManagedConnection.class,
"TIMED_GUID_LIFETIME",
new Long(EXPIRE_TIME));
ConnectionSettings.MULTICAST_PORT.setValue(10100);
UDP_ACCESS = new DatagramSocket();
UDP_ACCESS.setSoTimeout(500);
}
public static void setUpQRPTables() throws Exception {
QueryRouteTable qrt = new QueryRouteTable();
qrt.add("stanford");
qrt.add("susheel");
qrt.addIndivisible(HugeTestUtils.UNIQUE_SHA1.toString());
for (Iterator iter=qrt.encode(null).iterator(); iter.hasNext(); ) {
LEAF[0].send((RouteTableMessage)iter.next());
LEAF[0].flush();
}
qrt = new QueryRouteTable();
qrt.add("stanford");
qrt.addIndivisible(HugeTestUtils.UNIQUE_SHA1.toString());
for (Iterator iter=qrt.encode(null).iterator(); iter.hasNext(); ) {
LEAF[1].send((RouteTableMessage)iter.next());
LEAF[1].flush();
}
qrt = new QueryRouteTable();
qrt.add("leehsus");
qrt.add("stanford");
for (Iterator iter=qrt.encode(null).iterator(); iter.hasNext(); ) {
ULTRAPEER[0].send((RouteTableMessage)iter.next());
ULTRAPEER[0].flush();
}
qrt = new QueryRouteTable();
qrt.add("leehsus");
for (Iterator iter=qrt.encode(null).iterator(); iter.hasNext(); ) {
ULTRAPEER[1].send((RouteTableMessage)iter.next());
ULTRAPEER[1].flush();
}
}
// BEGIN TESTS
// ------------------------------------------------------
// PLEASE RUN THIS TEST FIRST!!!
public void testProxiesOnlyWhenSupposedTo() throws Exception {
// before we set up GUESS we should see that the UP does not proxy
//------------------------------
{
drainAll();
sendF(LEAF[1], MessagesSupportedVendorMessage.instance());
Thread.sleep(100); // wait for processing of msvm
QueryRequest query = QueryRequest.createQuery("stanford");
sendF(LEAF[1], query);
Thread.sleep(1000);
// the Ultrapeer should get it.
QueryRequest queryRec =
(QueryRequest) getFirstInstanceOfMessageType(ULTRAPEER[0],
QueryRequest.class);
assertNotNull(queryRec);
assertEquals(new GUID(query.getGUID()), new GUID(queryRec.getGUID()));
// shut off query
QueryStatusResponse resp =
new QueryStatusResponse(new GUID(queryRec.getGUID()), MAX_RESULTS);
sendF(LEAF[1], resp);
}
//------------------------------
PrivilegedAccessor.setValue(RouterService.getUdpService(),"_acceptedSolicitedIncoming",Boolean.TRUE);
PrivilegedAccessor.setValue(RouterService.getUdpService(),"_acceptedUnsolicitedIncoming",Boolean.TRUE);
Thread.sleep(500);
assertTrue(ROUTER_SERVICE.isGUESSCapable());
assertTrue(ROUTER_SERVICE.isOOBCapable());
//no one has sent a MessagesSupportedVM yet so no queries should be
//proxied
//------------------------------
{
drainAll();
QueryRequest query = QueryRequest.createQuery("stanford");
sendF(LEAF[0], query);
Thread.sleep(1000);
// the Ultrapeer should get it.
QueryRequest queryRec =
(QueryRequest) getFirstInstanceOfMessageType(ULTRAPEER[0],
QueryRequest.class);
assertNotNull(queryRec);
assertEquals(new GUID(query.getGUID()), new GUID(queryRec.getGUID()));
// shut off query
QueryStatusResponse resp =
new QueryStatusResponse(new GUID(queryRec.getGUID()), MAX_RESULTS);
sendF(LEAF[0], resp);
}
//------------------------------
//now send a MSM and make sure that the query is proxied
//------------------------------
{
drainAll();
sendF(LEAF[0], MessagesSupportedVendorMessage.instance());
Thread.sleep(100); // wait for processing of msvm
QueryRequest query = QueryRequest.createQuery("stanford");
sendF(LEAF[0], query);
Thread.sleep(1000);
// the Ultrapeer should get it and proxy it
QueryRequest queryRec =
(QueryRequest) getFirstInstanceOfMessageType(ULTRAPEER[0],
QueryRequest.class);
assertNotNull(queryRec);
assertTrue(queryRec.desiresOutOfBandReplies());
byte[] proxiedGuid = new byte[queryRec.getGUID().length];
System.arraycopy(queryRec.getGUID(), 0, proxiedGuid, 0,
proxiedGuid.length);
GUID.addressEncodeGuid(proxiedGuid, ROUTER_SERVICE.getAddress(),
ROUTER_SERVICE.getPort());
assertEquals(new GUID(proxiedGuid), new GUID(queryRec.getGUID()));
// shut off query
QueryStatusResponse resp =
new QueryStatusResponse(new GUID(queryRec.getGUID()), MAX_RESULTS);
sendF(LEAF[0], resp);
}
//------------------------------
//now send a OOB query and make sure it isn't proxied
//------------------------------
{
drainAll();
QueryRequest query =
QueryRequest.createOutOfBandQuery("leehsus",
LEAF[0].getInetAddress().getAddress(),
LEAF[0].getPort());
sendF(LEAF[0], query);
Thread.sleep(1000);
// the Ultrapeer should get it.
QueryRequest queryRec =
(QueryRequest) getFirstInstanceOfMessageType(ULTRAPEER[1],
QueryRequest.class);
assertNotNull(queryRec);
assertTrue(queryRec.desiresOutOfBandReplies());
assertEquals(new GUID(query.getGUID()), new GUID(queryRec.getGUID()));
assertEquals(LEAF[0].getPort(), queryRec.getReplyPort());
// shut off query
QueryStatusResponse resp =
new QueryStatusResponse(new GUID(queryRec.getGUID()), MAX_RESULTS);
sendF(LEAF[0], resp);
}
//------------------------------
//now send a 'no proxy' query and make sure it isn't proxied
//------------------------------
{
drainAll();
QueryRequest query = new QueryRequest(GUID.makeGuid(), (byte) 3,
"whatever", null, null, null,
null, false,
Message.N_UNKNOWN, false, 0,
true, 0);
sendF(LEAF[0], query);
Thread.sleep(1000);
// the Ultrapeer should get it.
QueryRequest queryRec =
(QueryRequest) getFirstInstanceOfMessageType(ULTRAPEER[1],
QueryRequest.class);
assertNotNull(queryRec);
assertTrue(queryRec.doNotProxy());
assertEquals(new GUID(query.getGUID()), new GUID(queryRec.getGUID()));
// shut off query
QueryStatusResponse resp =
new QueryStatusResponse(new GUID(queryRec.getGUID()), MAX_RESULTS);
sendF(LEAF[0], resp);
}
//------------------------------
//we should never proxy for a Ultrapeer
//now send a MSM and make sure that the query is proxied
//------------------------------
{
drainAll();
sendF(ULTRAPEER[0], MessagesSupportedVendorMessage.instance());
Thread.sleep(100); // wait for processing of msvm
QueryRequest query = QueryRequest.createQuery("stanford");
sendF(ULTRAPEER[0], query);
Thread.sleep(1000);
// the Leaf should get the non-OOB query
QueryRequest queryRec =
(QueryRequest) getFirstInstanceOfMessageType(LEAF[0],
QueryRequest.class);
assertNotNull(queryRec);
assertEquals(new GUID(query.getGUID()), new GUID(queryRec.getGUID()));
// no need shut off query
}
//------------------------------
}
// tests that:
// 1) routed TCP results are mapped
// 2) OOB results are acked and mapped
public void testBasicProxy() throws Exception {
drainAll();
QueryRequest query = QueryRequest.createQuery("stanford");
sendF(LEAF[0], query);
Thread.sleep(1000);
// the Ultrapeer should get it and proxy it
QueryRequest queryRec =
(QueryRequest) getFirstInstanceOfMessageType(ULTRAPEER[0],
QueryRequest.class);
assertNotNull(queryRec);
assertTrue(queryRec.desiresOutOfBandReplies());
byte[] proxiedGuid = new byte[queryRec.getGUID().length];
System.arraycopy(queryRec.getGUID(), 0, proxiedGuid, 0,
proxiedGuid.length);
GUID.addressEncodeGuid(proxiedGuid, ROUTER_SERVICE.getAddress(),
ROUTER_SERVICE.getPort());
assertEquals(new GUID(proxiedGuid), new GUID(queryRec.getGUID()));
// 1) route some TCP results back and make sure they are mapped back to
// the leaf
{
Response[] res = new Response[1];
for (int j = 0; j < res.length; j++)
res[j] = new Response(10, 10, "stanford0");
Message m =
new QueryReply(proxiedGuid, (byte) 3, 6355, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
sendF(ULTRAPEER[0], m);
Thread.sleep(1000); // processing wait
// leaf should get a reply with the correct guid
QueryReply queryRep =
(QueryReply) getFirstInstanceOfMessageType(LEAF[0],
QueryReply.class);
assertNotNull(queryRep);
assertEquals(new GUID(query.getGUID()),new GUID(queryRep.getGUID()));
assertEquals(((Response)queryRep.getResults().next()).getName(),
"stanford0");
assertEquals(2, queryRep.getTTL());
assertEquals(1, queryRep.getHops());
}
// 2) participate in a OOB exchange and make sure results are mapped
// back to the leaf
{
Response[] res = new Response[1];
for (int j = 0; j < res.length; j++)
res[j] = new Response(10, 10, "stanford1");
Message m =
new QueryReply(proxiedGuid, (byte) 3, 6356, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
// send a ReplyNumberVM
ReplyNumberVendorMessage replyNum =
new ReplyNumberVendorMessage(new GUID(proxiedGuid), 1);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
replyNum.write(baos);
DatagramPacket pack = new DatagramPacket(baos.toByteArray(),
baos.toByteArray().length,
InetAddress.getLocalHost(),
PORT);
UDP_ACCESS.send(pack);
// we expect an ACK
LimeACKVendorMessage ack = null;
while (ack == null) {
pack = new DatagramPacket(new byte[1000], 1000);
try {
UDP_ACCESS.receive(pack);
}
catch (IOException bad) {
fail("Did not get ack", bad);
}
InputStream in = new ByteArrayInputStream(pack.getData());
Message mTemp = Message.read(in);
if (mTemp instanceof LimeACKVendorMessage)
ack = (LimeACKVendorMessage) mTemp;
}
assertEquals(new GUID(proxiedGuid), new GUID(ack.getGUID()));
assertEquals(1, ack.getNumResults());
// send off the reply
baos = new ByteArrayOutputStream();
m.write(baos);
pack = new DatagramPacket(baos.toByteArray(),
baos.toByteArray().length,
InetAddress.getLocalHost(),
PORT);
UDP_ACCESS.send(pack);
// now we should get a reply at the leaf
QueryReply queryRep =
(QueryReply) getFirstInstanceOfMessageType(LEAF[0],
QueryReply.class);
assertNotNull(queryRep);
assertEquals(new GUID(query.getGUID()),new GUID(queryRep.getGUID()));
assertEquals(((Response)queryRep.getResults().next()).getName(),
"stanford1");
assertEquals(2, queryRep.getTTL());
assertEquals(1, queryRep.getHops());
}
// 3) shut off the query, make sure the OOB is bypassed but TCP is still
// sent
{
// shut off query
QueryStatusResponse resp =
new QueryStatusResponse(new GUID(queryRec.getGUID()),
MAX_RESULTS);
sendF(LEAF[0], resp);
Thread.sleep(500); // let it process
Response[] res = new Response[1];
for (int j = 0; j < res.length; j++)
res[j] = new Response(10, 10, "stanford2");
Message m =
new QueryReply(proxiedGuid, (byte) 3, 6356, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true,
true, false, false, null);
// send a ReplyNumberVM
ReplyNumberVendorMessage replyNum =
new ReplyNumberVendorMessage(new GUID(proxiedGuid), 1);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
replyNum.write(baos);
DatagramPacket pack = new DatagramPacket(baos.toByteArray(),
baos.toByteArray().length,
InetAddress.getLocalHost(),
PORT);
UDP_ACCESS.send(pack);
// we better not get an ACK
try {
while (true) {
pack = new DatagramPacket(new byte[1000], 1000);
UDP_ACCESS.receive(pack);
InputStream in = new ByteArrayInputStream(pack.getData());
Message mTemp = Message.read(in);
if (mTemp instanceof LimeACKVendorMessage)
fail("Should not get ACK!!!");
}
}
catch (InterruptedIOException expected) {}
// send via TCP, we better get it...
sendF(ULTRAPEER[0], m);
Thread.sleep(1000); // processing wait
// leaf should get a reply with the correct guid
QueryReply queryRep =
(QueryReply) getFirstInstanceOfMessageType(LEAF[0],
QueryReply.class);
assertNotNull(queryRep);
assertEquals(new GUID(query.getGUID()),new GUID(queryRep.getGUID()));
assertEquals(((Response)queryRep.getResults().next()).getName(),
"stanford2");
assertEquals(2, queryRep.getTTL());
assertEquals(1, queryRep.getHops());
}
}
// tests that the expirer works
public void testExpirer() throws Exception {
// see if anything is going to be expired
Class guidMapExpirer =
PrivilegedAccessor.getClass(ManagedConnection.class,
"GuidMapExpirer");
List expireList = (List) PrivilegedAccessor.getValue(guidMapExpirer,
"toExpire");
assertNotNull(expireList);
Thread.sleep(EXPIRE_TIME*2); // old guids should be expired...
synchronized (expireList) {
assertEquals(1, expireList.size());
// iterator through all the maps and confirm they are empty
Iterator iter = expireList.iterator();
while (iter.hasNext()) {
Map currMap = (Map) iter.next();
synchronized (currMap) {
assertTrue(currMap.isEmpty());
}
}
}
// now add a few queries and make sure some are expired but others not
{
QueryRequest query = QueryRequest.createQuery("sumeet");
sendF(LEAF[0], query);
Thread.sleep(500);
QueryStatusResponse resp =
new QueryStatusResponse(new GUID(query.getGUID()), MAX_RESULTS);
sendF(LEAF[0], resp);
Thread.sleep(500);
}
{
QueryRequest query = QueryRequest.createQuery("berlin");
sendF(LEAF[0], query);
Thread.sleep(500);
QueryStatusResponse resp =
new QueryStatusResponse(new GUID(query.getGUID()), MAX_RESULTS);
sendF(LEAF[0], resp);
Thread.sleep(500);
}
Thread.sleep(EXPIRE_TIME*2);
synchronized (expireList) {
// iterator through all the maps and confirm they are empty
Iterator iter = expireList.iterator();
while (iter.hasNext()) {
Map currMap = (Map) iter.next();
synchronized (currMap) {
assertTrue(currMap.isEmpty());
}
}
}
// close the leaf and make sure the MC purges it's guidmap
LEAF[0].close();
ROUTER_SERVICE.query(GUID.makeGuid(), "stanford");
Thread.sleep(2000);
assertTrue(expireList.isEmpty());
}
private final void sendF(Connection c, Message m) throws Exception {
c.send(m);
c.flush();
}
private static byte[] myIP() {
return new byte[] { (byte)127, (byte)0, 0, 1 };
}
}