package com.limegroup.gnutella.filters;
import org.limewire.collection.Buffer;
import org.limewire.io.GUID;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.PingRequest;
import com.limegroup.gnutella.messages.QueryRequest;
/**
* A spam filter that tries to eliminate duplicate packets from overzealous
* users. Since requests are not traceable, we have to use the following
* heuristic: two pings or queries are considered duplicates if they have
* similar GUIDs, arrived within BUF_SIZE messages of each other, and arrived
* not more than LAG milliseconds apart.
*/
public class DuplicateFilter implements SpamFilter {
private static Log LOG = LogFactory.getLog(DuplicateFilter.class);
/**
* The number of old messages to keep in memory. If this is too small, we
* won't be filtering properly. If this is too large, lookup becomes
* expensive.
*/
private static final int BUF_SIZE = 20;
/** The time, in milliseconds, allowed between similar messages. */
private static final int LAG = 500;
/**
* When comparing two messages, if the GUIDs of the two messages differ
* in more than TOLERANCE bytes, the second message will be allowed.
* If they differ in less than or equal to TOLERANCE bytes the second
* message will not be allowed thro'
*/
private static final int TOLERANCE = 2;
/**
* A list of the GUIDs of the last messages we saw, their timestamps and
* hop counts.
* INVARIANT: the youngest entries have largest timestamps
*/
private final Buffer<GUIDPair> guids = new Buffer<GUIDPair>(BUF_SIZE);
private int lag = LAG;
@Override
public boolean allow(Message m) {
//Do NOT apply this filter to pongs, query replies, or pushes,
//since many of those will (legally) have the same GUID.
if(!(m instanceof QueryRequest || m instanceof PingRequest))
return true;
GUIDPair me = new GUIDPair(m.getGUID(),
System.currentTimeMillis(), m.getHops());
//Consider all messages that came in within GUID_LAG milliseconds
//of this...
int size = guids.getSize();
for(int i = 0; i < size; i++) {
GUIDPair other = guids.get(i);
//The following assertion fails for mysterious reasons on the
//Macintosh. Also, it can fail if the user adjusts the clock, e.g.,
//for daylight savings time. Luckily it need not hold for the code
//to work correctly.
// Assert.that(me.time>=other.time,"Unexpected clock behavior");
if(me.time - other.time > lag)
//All remaining pings have smaller timestamps.
break;
//If different hops, keep looking
if(other.hops != me.hops)
continue;
//Are the GUIDs similar?
int misses = 0;
for(int j = 0; j < me.guid.length && misses <= TOLERANCE; j++) {
if(me.guid[j] != other.guid[j])
misses++;
}
if(misses <= TOLERANCE) {//really close GUIDS
if (LOG.isDebugEnabled())
LOG.debugf("not allowing: {0}", m);
guids.add(me);
return false;
}
}
guids.add(me);
return true;
}
// For testing
int getLag() {
return lag;
}
// For testing
void setLag(int lag) {
this.lag = lag;
}
private static class GUIDPair {
byte[] guid;
long time;
int hops;
GUIDPair(byte[] guid, long time, int hops) {
this.guid = guid;
this.time = time;
this.hops = hops;
}
@Override
public String toString() {
return "[" + (new GUID(guid)).toString() + ", " + time + "]";
}
}
}