package com.aionemu.gameserver.network.sequrity;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author NB4L1
*/
public final class FloodManager {
public static final class FloodFilter {
private final int _warnLimit;
private final int _rejectLimit;
private final int _tickLimit;
public FloodFilter(final int warnLimit, final int rejectLimit, final int tickLimit) {
_warnLimit = warnLimit;
_rejectLimit = rejectLimit;
_tickLimit = tickLimit;
}
public int getRejectLimit() {
return _rejectLimit;
}
public int getTickLimit() {
return _tickLimit;
}
public int getWarnLimit() {
return _warnLimit;
}
}
private final class LogEntry {
private final short[] _ticks = new short[_tickAmount];
private int _lastTick = getCurrentTick();
public int getCurrentTick() {
return (int) ((System.currentTimeMillis() - ZERO) / _tickLength);
}
public boolean isActive() {
return getCurrentTick() - _lastTick < _tickAmount * 10;
}
public Result isFlooding(final boolean increment) {
final int currentTick = getCurrentTick();
if (currentTick - _lastTick >= _ticks.length) {
_lastTick = currentTick;
Arrays.fill(_ticks, (short) 0);
} else if (_lastTick > currentTick) {
log.warn("The current tick (" + currentTick + ") is smaller than the last (" + _lastTick + ")!", new IllegalStateException());
_lastTick = currentTick;
} else
while (currentTick != _lastTick) {
_lastTick++;
_ticks[_lastTick % _ticks.length] = 0;
}
if (increment)
_ticks[_lastTick % _ticks.length]++;
for (FloodFilter filter : _filters) {
int previousSum = 0;
int currentSum = 0;
for (int i = 0; i <= filter.getTickLimit(); i++) {
int value = _ticks[(_lastTick - i) % _ticks.length];
if (i != 0)
previousSum += value;
if (i != filter.getTickLimit())
currentSum += value;
}
if (previousSum > filter.getRejectLimit() || currentSum > filter.getRejectLimit())
return Result.REJECTED;
if (previousSum > filter.getWarnLimit() || currentSum > filter.getWarnLimit())
return Result.WARNED;
}
return Result.ACCEPTED;
}
}
public static enum Result {
ACCEPTED,
WARNED,
REJECTED;
public static Result max(final Result r1, final Result r2) {
if (r1.ordinal() > r2.ordinal())
return r1;
return r2;
}
}
public final Logger log = LoggerFactory.getLogger(FloodManager.class);
private static final long ZERO = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
private final Map<String, LogEntry> _entries = new HashMap<>();
private final ReentrantLock _lock = new ReentrantLock();
private final int _tickLength;
private final int _tickAmount;
private final FloodFilter[] _filters;
public FloodManager(final int msecPerTick, final FloodFilter... filters) {
_tickLength = msecPerTick;
_filters = filters;
int max = 1;
for (FloodFilter filter : _filters)
max = Math.max(filter.getTickLimit() + 1, max);
_tickAmount = max;
NetFlusher.add(new Runnable() {
@Override
public void run() {
flush();
}
}, 60000);
}
private void flush() {
_lock.lock();
try {
for (Iterator<LogEntry> it = _entries.values().iterator(); it.hasNext();) {
if (it.next().isActive())
continue;
it.remove();
}
} finally {
_lock.unlock();
}
}
public Result isFlooding(final String key, final boolean increment) {
if (key == null || key.isEmpty())
return Result.REJECTED;
_lock.lock();
try {
LogEntry entry = _entries.get(key);
if (entry == null) {
entry = new LogEntry();
_entries.put(key, entry);
}
return entry.isFlooding(increment);
} finally {
_lock.unlock();
}
}
}