package com.limegroup.gnutella;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import junit.framework.Test;
import org.limewire.core.settings.SearchSettings;
import org.limewire.gnutella.tests.ActivityCallbackStub;
import org.limewire.gnutella.tests.LimeTestUtils;
import org.limewire.io.ConnectableImpl;
import org.limewire.io.GUID;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.limegroup.gnutella.connection.BlockingConnection;
import com.limegroup.gnutella.connection.RoutedConnection;
import com.limegroup.gnutella.downloader.RemoteFileDescFactory;
import com.limegroup.gnutella.helpers.UrnHelper;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryReplyFactory;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.messages.vendor.CapabilitiesVMFactory;
import com.limegroup.gnutella.messages.vendor.MessagesSupportedVendorMessage;
import com.limegroup.gnutella.messages.vendor.QueryStatusResponse;
import com.limegroup.gnutella.search.SearchResultHandler;
import com.limegroup.gnutella.spam.SpamManager;
import com.limegroup.gnutella.util.DataUtils;
/**
* Checks whether (multi)leaves avoid forwarding messages to ultrapeers, do
* redirects properly, etc. The test includes a leaf attached to 3
* Ultrapeers.
*/
@SuppressWarnings("all")
public class ClientSideLeafGuidanceTest extends ClientSideTestCase {
private final int REPORT_INTERVAL = SearchResultHandler.REPORT_INTERVAL;
private final int MAX_RESULTS = SearchResultHandler.MAX_RESULTS;
private SearchServices searchServices;
private QueryReplyFactory queryReplyFactory;
private ResponseFactory responseFactory;
private MyActivityCallback callback;
private SpamManager spamManager;
private MessagesSupportedVendorMessage messagesSupportedVendorMessage;
public ClientSideLeafGuidanceTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(ClientSideLeafGuidanceTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
@Override
protected void setUp() throws Exception {
Injector injector = LimeTestUtils.createInjector(MyActivityCallback.class);
super.setUp(injector);
searchServices = injector.getInstance(SearchServices.class);
queryReplyFactory = injector.getInstance(QueryReplyFactory.class);
responseFactory = injector.getInstance(ResponseFactory.class);
callback = (MyActivityCallback) injector.getInstance(ActivityCallback.class);
spamManager = injector.getInstance(SpamManager.class);
messagesSupportedVendorMessage = injector.getInstance(MessagesSupportedVendorMessage.class);
for (int i = 0; i < testUP.length; i++) {
// send a MessagesSupportedMessage
testUP[i].send(messagesSupportedVendorMessage);
// does this not flush on purpose? testBasicGuidance fails if we flush here.
}
}
private void establishGuidance() throws Exception {
CapabilitiesVMFactory cvmf = injector.getInstance(CapabilitiesVMFactory.class);
for (BlockingConnection up : testUP) {
up.send(cvmf.getCapabilitiesVM());
up.flush();
}
Thread.sleep(100);
ConnectionManager cm = injector.getInstance(ConnectionManager.class);
for (RoutedConnection c : cm.getInitializedConnections())
assertGreaterThan(0,c.getConnectionCapabilities().remoteHostSupportsLeafGuidance());
}
/** @return The first QueyrRequest received from this connection. If null
* is returned then it was never recieved (in a timely fashion).
*/
private QueryStatusResponse getFirstQueryStatus(BlockingConnection c)
throws BadPacketException, IOException {
return BlockingConnectionUtils.getFirstInstanceOfMessageType(c, QueryStatusResponse.class, TIMEOUT);
}
public void testBasicGuidance() throws Exception {
// spawn a query and make sure all UPs get it
GUID queryGuid = new GUID(searchServices.newQueryGUID());
searchServices.query(queryGuid.bytes(), "susheel");
Thread.sleep(250);
for (int i = 0; i < testUP.length; i++) {
QueryRequest qr = BlockingConnectionUtils.getFirstQueryRequest(testUP[i]);
assertNotNull(qr);
assertEquals(new GUID(qr.getGUID()), queryGuid);
}
// now send back results and make sure that we get a QueryStatus
// from the leaf
Message m = null;
// ensure that we'll get a QueryStatusResponse from the Responses
// we're sending.
assertGreaterThan(REPORT_INTERVAL, 6*testUP.length);
for (int i = 0; i < testUP.length; i++) {
Response[] res = new Response[] {
responseFactory.createResponse(10, 10, "susheel"+i, UrnHelper.SHA1),
responseFactory.createResponse(10, 10, "susheel smells good"+i, UrnHelper.SHA1),
responseFactory.createResponse(10, 10, "anita is sweet"+i, UrnHelper.SHA1),
responseFactory.createResponse(10, 10, "anita is prety"+i, UrnHelper.SHA1),
responseFactory.createResponse(10, 10, "susheel smells bad" + i, UrnHelper.SHA1),
responseFactory.createResponse(10, 10, "renu is sweet " + i, UrnHelper.SHA1),
responseFactory.createResponse(10, 10, "prety is spelled pretty " + i, UrnHelper.SHA1),
responseFactory.createResponse(10, 10, "go susheel go" + i, UrnHelper.SHA1),
responseFactory.createResponse(10, 10, "susheel runs fast" + i, UrnHelper.SHA1),
responseFactory.createResponse(10, 10, "susheel jumps high" + i, UrnHelper.SHA1),
responseFactory.createResponse(10, 10, "sleepy susheel" + i, UrnHelper.SHA1),
};
m = queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1, 6355,
myIP(), 0, res, GUID.makeGuid(), new byte[0], false, false,
true, true, false, false, null);
testUP[i].send(m);
testUP[i].flush();
}
// all UPs should get a QueryStatusResponse
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNotNull("up: " + i + " failed", stat);
assertEquals("up: " + i + " failed", new GUID(stat.getGUID()), queryGuid);
assertEquals("up: " + i + " failed", 5, stat.getNumResults());
}
// shut off the query....
searchServices.stopQuery(queryGuid);
// all UPs should get a QueryStatusResponse with 65535
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNotNull("up: " + i + " failed", stat);
assertEquals("up: " + i + " failed", new GUID(stat.getGUID()), queryGuid);
assertEquals("up: " + i + " failed", 65535, stat.getNumResults());
}
}
public void testAdvancedGuidance1() throws Exception {
establishGuidance();
for (int i = 0; i < testUP.length; i++)
BlockingConnectionUtils.drain(testUP[i]);
// spawn a query and make sure all UPs get it
GUID queryGuid = new GUID(searchServices.newQueryGUID());
searchServices.query(queryGuid.bytes(), "susheel daswanu");
for (int i = 0; i < testUP.length; i++) {
QueryRequest qr = BlockingConnectionUtils.getFirstQueryRequest(testUP[i]);
assertNotNull(qr);
assertEquals(new GUID(qr.getGUID()), queryGuid);
}
// now send back results and make sure that we get a QueryStatus
// from the leaf
Message m = null;
for (int i = 0; i < testUP.length; i++) {
//send enough responses per ultrapeer to shut off querying.
Response[] res = new Response[150/testUP.length + 10];
for (int j = 0; j < res.length; j++)
res[j] = responseFactory.createResponse(10, 10, "susheel good"+i+j, UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1, 6355,
myIP(), 0, res, GUID.makeGuid(), new byte[0], false, false,
true, true, false, false, null);
testUP[i].send(m);
testUP[i].flush();
System.out.println("sent response from "+testUP[i]);
}
// all UPs should get a QueryStatusResponse
boolean maxResultsEncountered = false;
for (int i = 0; i < testUP.length; i++) {
for (int j = 0; j < testUP.length; j++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[j]);
assertNotNull("failed on up: " + j, stat);
assertEquals("failed on up: " + j, new GUID(stat.getGUID()), queryGuid);
// depending on how far along the query is we could have a
// number or 65535 - the number 11 depends on settings such as
// REPORT_INTERVAL
if (stat.getNumResults() == MAX_RESULTS) {
maxResultsEncountered = true;
} else {
//assertEquals(11*(i+1), stat.getNumResults());
// there is no sane way this can be asserted.
}
}
}
assertTrue(maxResultsEncountered);
// now, even though we send more responses, we shoudl NOT get any more
// leaf guidance...
Response[] res = new Response[REPORT_INTERVAL*4];
for (int j = 0; j < res.length; j++)
res[j] = responseFactory.createResponse(10, 10, "anita is pretty"+j, UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1, 6355,
myIP(), 0, res, GUID.makeGuid(), new byte[0], false, false,
true, true, false, false, null);
testUP[0].send(m);
testUP[0].flush();
// no UPs should get a QueryStatusResponse
drainQSResponses();
}
public void testAdvancedGuidance2() throws Exception {
establishGuidance();
Message m = null;
for (int i = 0; i < testUP.length; i++)
BlockingConnectionUtils.drain(testUP[i]);
// spawn a query and make sure all UPs get it
GUID queryGuid = new GUID(searchServices.newQueryGUID());
searchServices.query(queryGuid.bytes(), "anita kesavan");
for (int i = 0; i < testUP.length; i++) {
QueryRequest qr = BlockingConnectionUtils.getFirstQueryRequest(testUP[i]);
assertNotNull(qr);
assertEquals(new GUID(qr.getGUID()), queryGuid);
}
// now send back results and make sure that we get a QueryStatus
// from the leaf
Response[] res = new Response[REPORT_INTERVAL*4];
for (int j = 0; j < res.length; j++)
res[j] = responseFactory.createResponse(10, 10, "anita is pretty"+j, UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1, 6355,
myIP(), 0, res, GUID.makeGuid(), new byte[0], false, false,
true, true, false, false, null);
testUP[0].send(m);
testUP[0].flush();
// all UPs should get a QueryStatusResponse
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNotNull("failed on up: " + i, stat);
assertEquals("failed on up: " + i, new GUID(stat.getGUID()), queryGuid);
assertEquals("failed on up: " + i, REPORT_INTERVAL, stat.getNumResults());
}
// now send just a few responses - less than the number of
// REPORT_INTERVAL - and confirm we don't get messages
res = new Response[REPORT_INTERVAL-1];
for (int j = 0; j < res.length; j++)
res[j] = responseFactory.createResponse(10, 10, "anita is sweet"+j, UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1, 6355,
myIP(), 0, res, GUID.makeGuid(), new byte[0], false, false,
true, true, false, false, null);
testUP[2].send(m);
testUP[2].flush();
// no UPs should get a QueryStatusResponse
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNull(stat);
}
// simply send 2 more responses....
res = new Response[2];
for (int j = 0; j < res.length; j++)
res[j] = responseFactory.createResponse(10, 10, "anita is young"+j, UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1, 6355,
myIP(), 0, res, GUID.makeGuid(), new byte[0], false, false,
true, true, false, false, null);
testUP[1].send(m);
testUP[1].flush();
// and all UPs should get a QueryStatusResponse
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNotNull(stat);
assertEquals(new GUID(stat.getGUID()), queryGuid);
assertEquals(REPORT_INTERVAL+((REPORT_INTERVAL+1)/4),
stat.getNumResults());
}
// shut off the query....
searchServices.stopQuery(queryGuid);
// all UPs should get a QueryStatusResponse with 65535
for (int i = 0; i < testUP.length; i++) {
QueryStatusResponse stat = getFirstQueryStatus(testUP[i]);
assertNotNull(stat);
assertEquals(new GUID(stat.getGUID()), queryGuid);
assertEquals(65535, stat.getNumResults());
}
// more results should not result in more status messages...
res = new Response[REPORT_INTERVAL*2];
for (int j = 0; j < res.length; j++)
res[j] = responseFactory.createResponse(10, 10, "anita is pretty"+j, UrnHelper.SHA1);
m = queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1, 6355,
myIP(), 0, res, GUID.makeGuid(), new byte[0], false, false,
true, true, false, false, null);
testUP[0].send(m);
testUP[0].flush();
// no UPs should get a QueryStatusResponse
drainQSResponses();
}
/**
* tests that spammy results are shown to the gui but are not counted
* in leaf guidance
*/
public void testSpamFiltering1() throws Exception {
SearchSettings.ENABLE_SPAM_FILTER.setValue(true);
SearchSettings.FILTER_SPAM_RESULTS.setValue(0.5f); // Strict
spamManager.clearFilterData();
callback.responses.clear();
final String query = "badgers";
final int size = 1234;
// Spawn a query and make sure the UPs get it
GUID queryGuid = spawnQuery(query);
// Mark a result as spam so the later results will be rated as spam
RemoteFileDescFactory rfdFactory =
injector.getInstance(RemoteFileDescFactory.class);
RemoteFileDesc rfd = rfdFactory.createRemoteFileDesc(
new ConnectableImpl("127.0.0.1", 6355, false),
1, query, size, DataUtils.EMPTY_GUID, 3, 3,
false, null, URN.NO_URN_SET, false, "ALT", 0l);
spamManager.handleUserMarkedSpam(new RemoteFileDesc[]{rfd});
assertTrue(rfd.isSpam());
// Send back results from the UP - they should be rated as spam
// because they have the same address and size as the spam result
Response[] res = new Response[REPORT_INTERVAL*4];
for (int i = 0; i < res.length; i++)
res[i] = responseFactory.createResponse(10, size, query + i, UrnHelper.SHA1);
QueryReply reply = queryReplyFactory.createQueryReply(
queryGuid.bytes(), (byte) 1, 6355, myIP(), 0, res,
GUID.makeGuid(), new byte[0], false, false, true, true,
false, false, null);
testUP[0].send(reply);
testUP[0].flush();
Thread.sleep(1000);
// the gui should be informed about the results
assertEquals(res.length, callback.responses.size());
// the UP should not get a QueryStatusResponse for spam results
QueryStatusResponse qsr = getFirstQueryStatus(testUP[0]);
assertNull(qsr);
}
/**
* tests that non-spammy results are shown to the gui and counted in
* leaf guidance
*/
public void testSpamFiltering2() throws Exception {
SearchSettings.ENABLE_SPAM_FILTER.setValue(true);
SearchSettings.FILTER_SPAM_RESULTS.setValue(0.5f); // Strict
spamManager.clearFilterData();
callback.responses.clear();
// spawn a query and make sure all UPs get it
GUID queryGuid = spawnQuery("anita kesavan");
// now send back results
Response[] res = new Response[REPORT_INTERVAL*4];
for (int i = 0; i < res.length; i++)
res[i] = responseFactory.createResponse(10, 10, "anita kesavan "+i, UrnHelper.SHA1);
QueryReply reply =
queryReplyFactory.createQueryReply(queryGuid.bytes(), (byte) 1,
6355, myIP(), 0, res, GUID.makeGuid(), new byte[0],
false, false, true, true, false, false, null);
testUP[0].send(reply);
testUP[0].flush();
Thread.sleep(1000);
// the gui should be informed about the results
assertEquals(res.length, callback.responses.size());
// the UP should get a QueryStatusResponse for non-spam results
QueryStatusResponse qsr = getFirstQueryStatus(testUP[0]);
assertNotNull(qsr);
assertEquals(res.length/4, qsr.getNumResults());
}
/**
* Spawns a query and makes sure all the UPs have received it
*
* @param query the query string
* @returns the GUID of the query
*/
private GUID spawnQuery(String query) throws Exception {
GUID queryGuid = new GUID(searchServices.newQueryGUID());
searchServices.query(queryGuid.bytes(), query);
Thread.sleep(250);
for(BlockingConnection up : testUP) {
QueryRequest qr = BlockingConnectionUtils.getFirstQueryRequest(up);
assertNotNull(qr);
assertEquals(new GUID(qr.getGUID()), queryGuid);
}
return queryGuid;
}
/**
* drains the ultrapeers for any QueryStatusResponses and fails if one is received.
* @throws Exception
*/
private void drainQSResponses() throws Exception {
BlockingConnectionUtils.failIfAnyArrive(testUP, QueryStatusResponse.class);
}
private static byte[] myIP() {
return new byte[] { (byte)127, (byte)0, (byte)0, (byte)1 };
}
public int getNumberOfPeers() {
return 3;
}
@Singleton
public static class MyActivityCallback extends ActivityCallbackStub {
public List responses = new ArrayList();
public void handleQueryResult(RemoteFileDesc rfdParam,
QueryReply queryReply,
Set locs) {
responses.add(rfdParam);
}
}
}