package com.limegroup.gnutella;
import java.io.ByteArrayInputStream;
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.net.SocketException;
import java.util.Random;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import junit.framework.Test;
import com.limegroup.gnutella.guess.QueryKey;
import com.limegroup.gnutella.guess.QueryKeyGenerator;
import com.limegroup.gnutella.messages.BadPacketException;
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.settings.ConnectionSettings;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
import com.limegroup.gnutella.stubs.MessageRouterStub;
import com.limegroup.gnutella.util.PrivilegedAccessor;
public class QueryUnicasterTest extends com.limegroup.gnutella.util.BaseTestCase {
private static final Log LOG = LogFactory.getLog(QueryUnicasterTest.class);
/**
* Constant for the size of UDP messages to accept -- dependent upon
* IP-layer fragmentation.
*/
private final int BUFFER_SIZE = 8192;
private final int SOCKET_TIMEOUT = 2*1000; // 2 second wait for a message
private final int NUM_UDP_LOOPS = 25;
private final RouterService _rs = new RouterService(new
ActivityCallbackStub(),
new MessageRouterStub());
private boolean _shouldRun = true;
private boolean shouldRun() {
return _shouldRun;
}
// produces should add(), consumers should firstElement()
private Vector _messages = new Vector();
public QueryUnicasterTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(QueryUnicasterTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
public void setUp() throws Exception {
_rs.start();
QueryUnicaster qu = QueryUnicaster.instance();
PrivilegedAccessor.invokeMethod( qu, "resetUnicastEndpointsAndQueries", null );
ConnectionSettings.DO_NOT_BOOTSTRAP.setValue(true);
ConnectionSettings.CONNECT_ON_STARTUP.setValue(false);
}
public void testConstruction() {
QueryUnicaster qu = QueryUnicaster.instance();
assertEquals("unexpected amount of unicast endpoints",
0, qu.getUnicastEndpoints().size());
}
public void testQueries() throws Exception {
_messages.clear();
_shouldRun=true;
// start udp hosts....
Thread[] udpLoopers = new Thread[NUM_UDP_LOOPS];
for (int i = 0; i < NUM_UDP_LOOPS; i++) {
final int index = i;
udpLoopers[i] = new Thread() {
public void run() {
udpLoop(5000 + index);
}
};
udpLoopers[i].start();
Thread.yield(); // let it run.
}
// add these endpoints....
InetAddress addr = null;
addr = InetAddress.getByName("127.0.0.1");
for (int i = 0; i < NUM_UDP_LOOPS; i++) {
QueryUnicaster.instance().addUnicastEndpoint(addr, 5000+i);
if (i % 5 == 0) {
try {
// give some time for queries to get out...
Thread.sleep(200);
}
catch (InterruptedException ignored) {}
}
}
// add a Query
QueryRequest qr = QueryRequest.createQuery("Susheel", (byte)2);
assertEquals("unexpected number of queries",
0, QueryUnicaster.instance().getQueryNumber() );
QueryUnicaster.instance().addQuery(qr, null);
assertEquals("unexpected number of queries",
1, QueryUnicaster.instance().getQueryNumber() );
// give udpLoopers time to execute
// get messages from vector, should be a message or a ping
// wait some seconds for thread to do work. this is not scientific
// but should do the job...
try {
Thread.sleep(30 * 1000);
}
catch (InterruptedException ignored) {}
int numMessages = 0, numQRs = 0, numPings = 0, numQKReqs = 0;
while (!_messages.isEmpty()) {
Message currMessage = (Message) _messages.remove(0);
numMessages++;
if (currMessage instanceof QueryRequest) {
QueryRequest currQR = (QueryRequest) currMessage;
assertEquals("unexpected query", "Susheel", currQR.getQuery() );
numQRs++;
}
else if (currMessage instanceof PingRequest) {
numPings++;
if (((PingRequest)currMessage).isQueryKeyRequest())
numQKReqs++;
}
else
fail("unexpected message: " + currMessage);
}
assertEquals("unexpected number of messages", numMessages, numPings + numQRs);
// can't send a Query without sending a Ping....
assertLessThanOrEquals("unexpected number of QRs", numPings, numQRs);
assertGreaterThan("unexpected number of QRs", 0, numQRs);
assertLessThanOrEquals("unexpected number of QRs", numQKReqs, numQRs);
if( LOG.isDebugEnabled() ) {
LOG.debug("QueryUnicasterTest.testQueries(): numMessages = " + numMessages);
LOG.debug("QueryUnicasterTest.testQueries(): numQRs = " + numQRs);
LOG.debug("QueryUnicasterTest.testQueries(): numPings = " + numPings);
}
// shut off udp listeners....
_shouldRun = false;
for (int i = 0; i < NUM_UDP_LOOPS; i++)
udpLoopers[i].interrupt();
// wait for them to stop...
try {
Thread.sleep(2 * 1000);
}
catch (InterruptedException ignored) {}
// get rid of old query...
QueryReply qRep = generateFakeReply(qr.getGUID(), 251);
QueryUnicaster.instance().handleQueryReply(qRep);
}
public void testResultMaxOut() throws Exception {
// clear out messages...
_messages.clear();
// start up threads...
_shouldRun = true;
// start udp hosts....
Thread[] udpLoopers = new Thread[NUM_UDP_LOOPS];
for (int i = 0; i < NUM_UDP_LOOPS; i++) {
final int index = i;
udpLoopers[i] = new Thread() {
public void run() {
udpLoop(5000 + index); // Bugfix: port 5000+i not 5500+i
}
};
udpLoopers[i].start();
Thread.yield(); // let it run.
}
// add a Query
QueryRequest qr = QueryRequest.createQuery("Daswani", (byte)2);
QueryUnicaster.instance().addQuery(qr, null);
// add these endpoints....
InetAddress addr = null;
addr = InetAddress.getByName("127.0.0.1");
for (int i = 0; i < NUM_UDP_LOOPS; i++) {
QueryUnicaster.instance().addUnicastEndpoint(addr, 5000+i);
if (i % 5 == 0) {
try {
// give some time for queries to get out...
Thread.sleep(200);
}
catch (InterruptedException ignored) {}
}
//add some results...
// BugFix: now works despite different NUM_UDP_LOOPS values
int low=250/NUM_UDP_LOOPS+1; int hi=254/NUM_UDP_LOOPS+1;
if( low<25 )low=25; if( hi<35 )hi=35;
QueryReply qRep = generateFakeReply(qr.getGUID(),
getNumberBetween(low, hi));
QueryUnicaster.instance().handleQueryReply(qRep);
}
// give udpLoopers time to execute
// get messages from vector, should be a message or a ping
// wait some seconds for thread to do work. this is not scientific
// but should do the job...
try {
Thread.sleep(30 * 1000);
assertEquals("unexpected number of queries",
0, QueryUnicaster.instance().getQueryNumber() );
}
catch (InterruptedException ignored) {}
int numMessages = 0, numQRs = 0, numPings = 0;
while (!_messages.isEmpty()) {
Message currMessage = (Message) _messages.remove(0);
numMessages++;
if (currMessage instanceof QueryRequest) {
QueryRequest currQR = (QueryRequest) currMessage;
assertTrue(currQR.getQuery().equals("Daswani"));
numQRs++;
}
else if (currMessage instanceof PingRequest) {
numPings++;
}
else
fail("unexpected message: " + currMessage);
}
assertEquals("unexpected number of messages", numMessages, numPings + numQRs);
assertLessThan("unexpected number of QRs", 11, numQRs); // 15 * 25 >> 250
// If we are using fewer than 11 UDP endpoints, then ALL of them will have been
// used to reply to the UnicastQuery, so none will be left. Therefore, only
// apply the following assert if there are more left over.
if( NUM_UDP_LOOPS>10 )
assertGreaterThan("unexpected endpoint size",
0, QueryUnicaster.instance().getUnicastEndpoints().size() );
if( LOG.isDebugEnabled() ) {
LOG.debug("QueryUnicasterTest.testQueries(): numMessages = " +
numMessages);
LOG.debug("QueryUnicasterTest.testQueries(): numQRs = " +
numQRs);
LOG.debug("QueryUnicasterTest.testQueries(): numPings = " +
numPings);
}
// shut off udp listeners....
_shouldRun = false;
for (int i = 0; i < NUM_UDP_LOOPS; i++)
udpLoopers[i].interrupt();
// wait for them to stop...
try {
Thread.sleep(2 * 1000);
}
catch (InterruptedException ignored) {}
}
private QueryReply generateFakeReply(byte[] guid, int numResponses) {
Response[] resps = new Response[numResponses];
for (int i = 0; i< resps.length; i++)
resps[i] = new Response(i, i, ""+i);
byte[] ip = {(byte)127, (byte)0, (byte)0, (byte)1};
QueryReply toReturn = new QueryReply(guid,
(byte) 2,
1, ip, 0, resps,
GUID.makeGuid(), false);
return toReturn;
}
/** returns a number from low to high (both inclusive).
*/
private int getNumberBetween(int low, int high) {
Random rand = new Random();
int retInt = low - 1;
while (retInt < low)
retInt = rand.nextInt(high+1);
return retInt;
}
private static byte[] localhost = {(byte)127,(byte)0,(byte)0,(byte)1};
/**
* Busy loop that listens on a port for udp messages and then logs them.
*/
private void udpLoop(int port) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket(port);
socket.setSoTimeout(1000);
if( LOG.isDebugEnabled() )
LOG.debug("QueryUnicasterTest.udpLoop(): listening on port " +
port);
//socket.setSoTimeout(SOCKET_TIMEOUT);
}
catch (SocketException e) {
if( LOG.isDebugEnabled() )
LOG.debug("QueryUnicasterTest.udpLoop(): couldn't listen on port " +
port);
return;
}
catch (RuntimeException e) {
if( LOG.isDebugEnabled() )
LOG.debug("QueryUnicasterTest.udpLoop(): couldn't listen on port " +
port);
return;
}
byte[] datagramBytes = new byte[BUFFER_SIZE];
DatagramPacket datagram = new DatagramPacket(datagramBytes,
BUFFER_SIZE);
QueryKeyGenerator secretKey = QueryKey.createKeyGenerator();
while (shouldRun()) {
try {
socket.receive(datagram);
byte[] data = datagram.getData();
int length = datagram.getLength();
try {
// construct a message out of it...
InputStream in = new ByteArrayInputStream(data);
Message message = Message.read(in);
if(message == null) continue;
if (message instanceof PingRequest) {
PingRequest pr = (PingRequest)message;
pr.hop(); // need to hop it!!
if (pr.isQueryKeyRequest()) {
// send a QueryKey back!!!
QueryKey qk =
QueryKey.getQueryKey(datagram.getAddress(),
datagram.getPort(),
secretKey);
PingReply pRep =
PingReply.createQueryKeyReply(pr.getGUID(),
(byte)1,
port,
localhost,
2,2, true,
qk);
pRep.hop();
LOG.debug("QueryUnicasterTest.udpLoop(): sending QK.");
QueryUnicaster.instance().handleQueryKeyPong(pRep);
}
}
// log the message....
synchronized (_messages) {
if( LOG.isDebugEnabled() )
LOG.debug(" ** Adding message to _messages queue (newSize="+(_messages.size()+1)+") m="+message);
_messages.add(message);
_messages.notify();
}
} catch(BadPacketException e) {
continue;
}
}
catch (InterruptedIOException e) {
continue;
}
catch (IOException e) {
continue;
}
}
if( LOG.isDebugEnabled() )
LOG.debug("QueryUnicasterTest.udpLoop(): closing down port " +
port);
socket.close();
}
}