package net.i2p.router;
import net.i2p.router.util.DecayingBloomFilter;
import net.i2p.router.util.DecayingHashSet;
import net.i2p.util.Log;
/**
* Singleton to manage the logic (and historical data) to determine whether a message
* is valid or not (meaning it isn't expired and hasn't already been received). We'll
* need a revamp once we start dealing with long message expirations (since it might
* involve keeping a significant number of entries in memory), but that probably won't
* be necessary until I2P 3.0.
*
*/
public class MessageValidator {
private final Log _log;
private final RouterContext _context;
private DecayingBloomFilter _filter;
public MessageValidator(RouterContext context) {
_log = context.logManager().getLog(MessageValidator.class);
_context = context;
context.statManager().createRateStat("router.duplicateMessageId", "Note that a duplicate messageId was received", "Router",
new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
context.statManager().createRateStat("router.invalidMessageTime", "Note that a message outside the valid range was received", "Router",
new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
}
/**
* Determine if this message should be accepted as valid (not expired, not a duplicate)
*
* @return reason why the message is invalid (or null if the message is valid)
*/
public String validateMessage(long messageId, long expiration) {
String msg = validateMessage(expiration);
if (msg != null)
return msg;
boolean isDuplicate = noteReception(messageId, expiration);
if (isDuplicate) {
if (_log.shouldLog(Log.INFO))
_log.info("Rejecting message " + messageId + " because it is a duplicate", new Exception("Duplicate origin"));
_context.statManager().addRateData("router.duplicateMessageId", 1);
return "duplicate";
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Accepting message " + messageId + " because it is NOT a duplicate", new Exception("Original origin"));
return null;
}
}
/**
* Only check the expiration for the message
*/
public String validateMessage(long expiration) {
long now = _context.clock().now();
if (now - (Router.CLOCK_FUDGE_FACTOR * 3 / 2) >= expiration) {
if (_log.shouldLog(Log.INFO))
_log.info("Rejecting message because it expired " + (now-expiration) + "ms ago");
_context.statManager().addRateData("router.invalidMessageTime", (now-expiration));
return "expired " + (now-expiration) + "ms ago";
} else if (now + 4*Router.CLOCK_FUDGE_FACTOR < expiration) {
if (_log.shouldLog(Log.INFO))
_log.info("Rejecting message because it will expire too far in the future (" + (expiration-now) + "ms)");
_context.statManager().addRateData("router.invalidMessageTime", (now-expiration));
return "expire too far in the future (" + (expiration-now) + "ms)";
}
return null;
}
private static final long TIME_MASK = 0xFFFFFC00;
/**
* Note that we've received the message (which has the expiration given).
* This functionality will need to be reworked for I2P 3.0 when we take into
* consideration messages with significant user specified delays (since we dont
* want to keep an infinite number of messages in RAM, etc)
*
* @return true if we HAVE already seen this message, false if not
*/
private boolean noteReception(long messageId, long messageExpiration) {
long val = messageId;
// tweak the high order bits with the message expiration /seconds/
////val ^= (messageExpiration & TIME_MASK) << 16;
val ^= (messageExpiration & TIME_MASK);
boolean dup = _filter.add(val);
if (dup && _log.shouldLog(Log.WARN)) {
_log.warn("Duplicate with " + _filter.getCurrentDuplicateCount()
+ " other dups, " + _filter.getInsertedCount()
+ " other entries, and a false positive rate of "
+ _filter.getFalsePositiveRate());
}
return dup;
}
public synchronized void startup() {
_filter = new DecayingHashSet(_context, (int)Router.CLOCK_FUDGE_FACTOR * 2, 8, "RouterMV");
}
synchronized void shutdown() {
_filter.stopDecaying();
}
}