package com.limegroup.gnutella.spam;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import junit.framework.Test;
import org.limewire.core.settings.FilterSettings;
import org.limewire.core.settings.SearchSettings;
import org.limewire.gnutella.tests.LimeTestCase;
import org.limewire.gnutella.tests.LimeTestUtils;
import org.limewire.io.ConnectableImpl;
import org.limewire.io.GUID;
import com.google.inject.Injector;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.Response;
import com.limegroup.gnutella.ResponseFactory;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.downloader.RemoteFileDescFactory;
import com.limegroup.gnutella.filters.SpamFilter;
import com.limegroup.gnutella.filters.URNFilter;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryReplyFactory;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.messages.QueryRequestFactory;
import com.limegroup.gnutella.xml.LimeXMLDocument;
import com.limegroup.gnutella.xml.LimeXMLDocumentFactory;
public class SpamManagerTest extends LimeTestCase {
/** keywords */
private static final String badger = " badger ";
private static final String mushroom = " mushroom ";
private static final String snake = " snake ";
/** addresses */
private static final String addr1 = "1.1.1.1";
private static final String addr2 = "2.2.2.2";
private static final String addr3 = "3.3.3.3";
private static final String addr4 = "4.4.4.4";
/** ports */
private static final int port1 = 1, port2 = 2, port3 = 3, port4 = 4;
/** sizes */
private static final int size1 = 1000;
private static final int size2 = 2000;
private static final int size3 = 3000;
private static final int size4 = 4000;
/** xml docs */
private static final String xml1 = "<?xml version='1.0'?>" +
"<audios xsi:noNamespaceSchemaLocation=" +
"'http://www.limewire.com/schemas/audio.xsd'>" +
"<audio title='badger'></audio></audios>";
private static final String xml2 = "<?xml version='1.0'?>" +
"<videos xsi:noNamespaceSchemaLocation=" +
"'http://www.limewire.com/schemas/video.xsd'>" +
"<video title='mushroom'></video></videos>";
private static URN urn1, urn2, urn3, urn4, spamUrn;
private static LimeXMLDocument doc1, doc2;
private Injector injector;
private SpamManager manager;
private LimeXMLDocumentFactory limeXMLDocumentFactory;
private QueryRequestFactory queryRequestFactory;
private RemoteFileDescFactory remoteFileDescFactory;
public SpamManagerTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(SpamManagerTest.class);
}
@Override
protected void setUp() throws Exception {
SearchSettings.ENABLE_SPAM_FILTER.setValue(true);
SearchSettings.FILTER_SPAM_RESULTS.setValue(0.5f);
urn1 = URN.createSHA1Urn("urn:sha1:PLSTHIPQGSSZTS5FJUPAKUZWUGYQYPFB");
urn2 = URN.createSHA1Urn("urn:sha1:ZLSTHIPQGSSZTS5FJUPAKUZWUGZQYPFB");
urn3 = URN.createSHA1Urn("urn:sha1:YLSTHIPQGSSZTS5FJUPAKUZWUGZQYPFB");
urn4 = URN.createSHA1Urn("urn:sha1:XLSTHIPQGSSZTS5FJUPAKUZWUGZQYPFB");
// Blacklist a URN
String spam = "WLSTHIPQGSSZTS5FJUPAKUZWUGZQYPFB";
spamUrn = URN.createSHA1Urn("urn:sha1:" + spam);
FilterSettings.FILTERED_URNS_LOCAL.set(new String[] { spam });
injector = LimeTestUtils.createInjectorNonEagerly();
limeXMLDocumentFactory = injector.getInstance(LimeXMLDocumentFactory.class);
queryRequestFactory = injector.getInstance(QueryRequestFactory.class);
remoteFileDescFactory = injector.getInstance(RemoteFileDescFactory.class);
manager = injector.getInstance(SpamManager.class);
manager.clearFilterData();
doc1 = limeXMLDocumentFactory.createLimeXMLDocument(xml1);
doc2 = limeXMLDocumentFactory.createLimeXMLDocument(xml2);
}
/**
* Tests that incoming results start with a zero spam rating
*/
public void testStartsAtZero() throws Exception {
RemoteFileDesc result = createRFD(addr1, port1, badger, null, urn1, size1);
assertFalse(result.isSpam());
assertEquals(0f, result.getSpamRating());
}
/**
* Tests that when the user marks one result as spam, it affects the
* rating of subsequent related results, but not existing results or
* subsequent unrelated results
*/
public void testSetSpam() throws Exception {
// Two results arrive
RemoteFileDesc marked = createRFD(addr1, port1, badger, null, urn1, size1);
RemoteFileDesc related1 = createRFD(addr2, port2, badger+mushroom, null, urn2, size2);
// Save the spam ratings before the user's action
float markedRating = marked.getSpamRating();
float related1Rating = related1.getSpamRating();
// The user marks the first result as spam - its rating should increase
// but the rating of the related result should not
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
assertGreaterThan(markedRating, marked.getSpamRating());
assertFalse(related1.isSpam());
assertEquals(related1Rating, related1.getSpamRating());
// Another related result arrives after the user's action - it should
// receive a spam rating greater than zero but less than the rating
// of the spam result
RemoteFileDesc related2 = createRFD(addr3, port3, badger+snake, null, urn3, size3);
assertGreaterThan(0f, related2.getSpamRating());
assertLessThan(marked.getSpamRating(), related2.getSpamRating());
// An unrelated result - it should receive a zero spam rating
RemoteFileDesc unrelated = createRFD(addr4, port4, mushroom+snake, null, urn4, size4);
assertFalse(unrelated.isSpam());
assertEquals(0f, unrelated.getSpamRating());
}
/**
* Tests that when the user marks one result as not spam, it affects the
* rating of subsequent related results, but not existing results
*/
public void testSetNotSpam() throws Exception {
// A result arrives and the user marks it as spam
RemoteFileDesc marked = createRFD(addr1, port1, badger, null, urn1, size1);
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
float markedRating = marked.getSpamRating();
assertGreaterThan(0f, markedRating);
// A related result arrives - it should receive a non-zero spam rating
RemoteFileDesc related1 = createRFD(addr2, port2, badger+mushroom, null, urn2, size2);
float related1Rating = related1.getSpamRating();
assertGreaterThan(0f, related1Rating);
assertLessThan(markedRating, related1Rating);
// Now the user marks the first result as NOT spam - its rating should
// decrease but the rating of the related result should not
manager.handleUserMarkedGood(new RemoteFileDesc[]{marked});
assertFalse(marked.isSpam());
assertLessThan(markedRating, marked.getSpamRating());
assertEquals(related1Rating, related1.getSpamRating());
// Another related result arrives after the user's action - it should
// receive a lower rating than the first related result
RemoteFileDesc related2 = createRFD(addr3, port3, badger+snake, null, urn3, size3);
assertLessThan(related1Rating, related2.getSpamRating());
}
/**
* Tests that the URN is sufficient to identify spam
*/
public void testUrnIsEnough() throws Exception {
// A result arrives and the user marks it as spam
RemoteFileDesc marked = createRFD(addr1, port1, badger, null, urn1, size1);
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
assertGreaterThan(0f, marked.getSpamRating());
// Another result arrives, with the same URN but nothing else in
// common - it should be marked as spam
RemoteFileDesc sameURN = createRFD(addr2, port2, mushroom, null, urn1, size2);
assertTrue(sameURN.isSpam());
assertGreaterThan(0f, sameURN.getSpamRating());
}
/**
* Tests that the ratings are lowered for keywords the user searches for
*/
public void testQueryLowers() throws Exception {
// A result arrives and the user marks it as spam
RemoteFileDesc marked = createRFD(addr1, port1, badger, null, urn1, size1);
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
assertGreaterThan(0f, marked.getSpamRating());
// A related result arrives - it should receive a non-zero spam rating
RemoteFileDesc related1 = createRFD(addr2, port2, badger+mushroom, null, urn2, size2);
assertGreaterThan(0f, related1.getSpamRating());
assertLessThan(marked.getSpamRating(), related1.getSpamRating());
// Now the user issues a query with related keywords
QueryRequest qr = queryRequestFactory.createQuery(badger+snake);
manager.startedQuery(qr);
// Another related result arrives - it should receive a lower rating
// than the first related result
RemoteFileDesc related2 = createRFD(addr3, port3, badger+mushroom, null, urn3, size3);
assertLessThan(related1.getSpamRating(), related2.getSpamRating());
}
/**
* Tests that the address (not port) of the result affects the spam rating
*/
public void testAddressAffects() throws Exception {
// A result arrives and the user marks it as spam
RemoteFileDesc marked = createRFD(addr1, port1, badger, null, urn1, size1);
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
assertGreaterThan(0f, marked.getSpamRating());
// A result with nothing in common but the address should receive a
// non-zero rating
RemoteFileDesc sameAddress = createRFD(addr1, port2, mushroom, null, urn2, size2);
assertGreaterThan(0f, sameAddress.getSpamRating());
assertLessThan(marked.getSpamRating(), sameAddress.getSpamRating());
// A result with nothing in common should receive a zero spam rating
RemoteFileDesc unrelated = createRFD(addr3, port3, snake, null, urn3, size3);
assertFalse(unrelated.isSpam());
assertEquals(0f, unrelated.getSpamRating());
}
/**
* Tests that the size of the result affects the spam rating
*/
public void testSizeAffects() throws Exception {
// A result arrives and the user marks it as spam
RemoteFileDesc marked = createRFD(addr1, port1, badger, null, urn1, size1);
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
assertGreaterThan(0f, marked.getSpamRating());
// A result with nothing in common but the size should receive a
// non-zero rating
RemoteFileDesc sameAddress = createRFD(addr2, port2, mushroom, null, urn2, size1);
assertGreaterThan(0f, sameAddress.getSpamRating());
assertLessThan(marked.getSpamRating(), sameAddress.getSpamRating());
// A result with nothing in common should receive a zero spam rating
RemoteFileDesc unrelated = createRFD(addr3, port3, snake, null, urn3, size3);
assertFalse(unrelated.isSpam());
assertEquals(0f, unrelated.getSpamRating());
}
/**
* Tests that the approximate size of the result affects the spam rating
*/
public void testApproximateSizeAffects() throws Exception {
int size5 = 1024, size6 = 1025, size7 = 2048;
// A result arrives and the user marks it as spam
RemoteFileDesc marked = createRFD(addr1, port1, badger, null, urn1, size5);
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
assertGreaterThan(0f, marked.getSpamRating());
// A result with nothing in common but similar in size should receive
// a non-zero rating
RemoteFileDesc sameAddress = createRFD(addr2, port2, mushroom, null, urn2, size6);
assertGreaterThan(0f, sameAddress.getSpamRating());
assertLessThan(marked.getSpamRating(), sameAddress.getSpamRating());
// A result with nothing in common should receive a zero spam rating
RemoteFileDesc unrelated = createRFD(addr3, port3, snake, null, urn3, size7);
assertFalse(unrelated.isSpam());
assertEquals(0f, unrelated.getSpamRating());
}
/**
* Tests that the client GUID of the result affects the spam rating
*/
public void testClientGUIDAffects() throws Exception {
byte[] guid1 = GUID.makeGuid();
byte[] guid2 = GUID.makeGuid();
// A result arrives and the user marks it as spam
RemoteFileDesc marked =
createRFD(addr1, port1, badger, null, urn1, size1, guid1);
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
assertGreaterThan(0f, marked.getSpamRating());
// A result with nothing in common but the client GUID should receive
// a non-zero rating
RemoteFileDesc sameGUID =
createRFD(addr2, port2, mushroom, null, urn2, size2, guid1);
assertGreaterThan(0f, sameGUID.getSpamRating());
assertLessThan(marked.getSpamRating(), sameGUID.getSpamRating());
// A result with nothing in common should receive a zero spam rating
RemoteFileDesc unrelated =
createRFD(addr3, port3, snake, null, urn3, size3, guid2);
assertFalse(unrelated.isSpam());
assertEquals(0f, unrelated.getSpamRating());
}
/**
* Tests that any XML documents in the result affect the spam rating
*/
public void testXMLAffects() throws Exception {
// A result arrives and the user marks it as spam
RemoteFileDesc marked = createRFD(addr1, port1, badger, doc1, urn1, size1);
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
assertGreaterThan(0f, marked.getSpamRating());
// A result with nothing in common but the XML should receive a
// non-zero rating
RemoteFileDesc sameAddress = createRFD(addr2, port2, mushroom, doc1, urn2, size2);
assertGreaterThan(0f, sameAddress.getSpamRating());
assertLessThan(marked.getSpamRating(), sameAddress.getSpamRating());
// A result with nothing in common should receive a zero spam rating
RemoteFileDesc unrelated = createRFD(addr3, port3, snake, doc2, urn3, size3);
assertFalse(unrelated.isSpam());
assertEquals(0f, unrelated.getSpamRating());
}
/**
* Tests that the extension of the result affects the spam rating less
* than an ordinary keyword
*/
public void testKeywordBeatsExtension() throws Exception {
String ext1 = ".foo", ext2 = ".bar";
// A result arrives and the user marks it as spam
RemoteFileDesc marked = createRFD(addr1, port1, badger+ext1, null, urn1, size1);
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
assertGreaterThan(0f, marked.getSpamRating());
// A result with nothing in common but the extension should receive a
// non-zero rating
RemoteFileDesc sameExt = createRFD(addr2, port2, mushroom+ext1, null, urn2, size2);
assertGreaterThan(0f, sameExt.getSpamRating());
assertLessThan(marked.getSpamRating(), sameExt.getSpamRating());
// A result with nothing in common but an ordinary keyword should
// receive a non-zero rating
RemoteFileDesc sameWord = createRFD(addr3, port3, badger+ext2, null, urn3, size3);
assertGreaterThan(0f, sameWord.getSpamRating());
assertLessThan(marked.getSpamRating(), sameWord.getSpamRating());
// The extension should have less effect than the keyword
assertLessThan(sameWord.getSpamRating(), sameExt.getSpamRating());
// A result with nothing in common should receive a zero spam rating
RemoteFileDesc unrelated = createRFD(addr4, port4, snake, null, urn4, size4);
assertFalse(unrelated.isSpam());
assertEquals(0f, unrelated.getSpamRating());
}
/**
* Tests that private (LAN) addresses are ignored
*/
public void testPrivateAddressIgnored() throws Exception {
String privateAddress = "192.168.0.1";
// A result arrives and the user marks it as spam
RemoteFileDesc marked = createRFD(privateAddress, port1, badger, null, urn1, size1);
manager.handleUserMarkedSpam(new RemoteFileDesc[]{marked});
assertTrue(marked.isSpam());
assertGreaterThan(0f, marked.getSpamRating());
// A result with nothing in common but the private address should
// receive a zero rating
RemoteFileDesc unrelated = createRFD(privateAddress, port2, mushroom, null, urn2, size2);
assertFalse(unrelated.isSpam());
assertEquals(0f, unrelated.getSpamRating());
}
/**
* Tests that the IP blacklist no longer affects the spam rating - results
* from blacklisted IPs should be dropped before reaching this stage
*/
public void testAddressBlacklistDoesNotAffect() throws Exception {
FilterSettings.HOSTILE_IPS.set(new String[] {addr1});
FilterSettings.BLACK_LISTED_IP_ADDRESSES.set(new String[] {addr2});
FilterSettings.WHITE_LISTED_IP_ADDRESSES.set(new String[] {addr3});
// A reply from a hostile address should receive a zero rating
RemoteFileDesc hostile = createRFD(addr1, port1, badger, null, urn1, size1);
assertFalse(hostile.isSpam());
assertEquals(0f, hostile.getSpamRating());
// A reply from a blacklisted address should receive a zero rating
RemoteFileDesc black = createRFD(addr2, port2, badger, null, urn2, size2);
assertFalse(black.isSpam());
assertEquals(0f, black.getSpamRating());
// A reply from a whitelisted address should receive a zero rating
RemoteFileDesc white = createRFD(addr3, port3, badger, null, urn3, size3);
assertFalse(white.isSpam());
assertEquals(0f, white.getSpamRating());
FilterSettings.HOSTILE_IPS.revertToDefault();
FilterSettings.BLACK_LISTED_IP_ADDRESSES.revertToDefault();
FilterSettings.WHITE_LISTED_IP_ADDRESSES.revertToDefault();
}
/**
* Tests that the URN blacklist affects the spam rating
*/
public void testUrnBlacklistAffects() throws Exception {
// A query reply with a blacklisted URN arrives
ResponseFactory responseFactory =
injector.getInstance(ResponseFactory.class);
Response response =
responseFactory.createResponse(0, size1, badger+snake, spamUrn);
QueryReplyFactory queryReplyFactory =
injector.getInstance(QueryReplyFactory.class);
QueryReply reply = queryReplyFactory.createQueryReply(GUID.makeGuid(),
(byte)0, port1, new byte[] {1, 1, 1, 1}, 0,
new Response[] {response}, GUID.makeGuid(), false, false,
false, false, false, false);
// The URN filter should block the result and mark it as spam
URNFilter urnFilter = injector.getInstance(URNFilter.class);
final CountDownLatch loaded = new CountDownLatch(1);
SpamFilter.LoadCallback callback = new SpamFilter.LoadCallback() {
@Override
public void spamFilterLoaded() {
loaded.countDown();
}
};
urnFilter.refreshURNs(callback);
assertTrue(loaded.await(10, TimeUnit.SECONDS));
assertEquals(0, manager.getRatingTable().size());
assertFalse(urnFilter.allow(reply));
assertGreaterThan(0, manager.getRatingTable().size());
// A result with the same keywords as the spam result should receive a
// zero spam rating
RemoteFileDesc sameName =
createRFD(addr2, port2, badger+snake, null, urn2, size2);
assertFalse(sameName.isSpam());
assertEquals(0f, sameName.getSpamRating());
// A result from the same address as the spam result should received a
// non-zero spam rating
RemoteFileDesc sameAddr =
createRFD(addr1, port3, badger+snake, null, urn3, size3);
assertGreaterThan(0f, sameAddr.getSpamRating());
// A result with the same size as the spam result should receive a
// non-zero spam rating
RemoteFileDesc sameSize =
createRFD(addr4, port4, badger+snake, null, urn4, size1);
assertGreaterThan(0f, sameSize.getSpamRating());
// An unrelated result should receive a zero spam rating
RemoteFileDesc unrelated =
createRFD(addr4, port4, badger+snake, null, urn4, size4);
assertFalse(unrelated.isSpam());
assertEquals(0f, unrelated.getSpamRating());
}
private RemoteFileDesc createRFD(String addr, int port, String name,
LimeXMLDocument doc, URN urn, int size) throws Exception {
return createRFD(addr, port, name, doc, urn, size, GUID.makeGuid());
}
private RemoteFileDesc createRFD(String addr, int port, String name,
LimeXMLDocument doc, URN urn, int size, byte[] guid)
throws Exception {
Set<URN> urns = new HashSet<URN>();
urns.add(urn);
RemoteFileDesc rfd =
remoteFileDescFactory.createRemoteFileDesc(
new ConnectableImpl(addr, port, false), 1, name, size,
guid, 3, 3, false, doc, urns, false, "ALT", 0);
// This would normally be called by the SearchResultHandler
manager.calculateSpamRating(rfd);
return rfd;
}
}