package net.i2p.router.transport.udp;
import java.util.concurrent.BlockingQueue;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.I2NPMessageException;
import net.i2p.data.i2np.I2NPMessageHandler;
import net.i2p.data.i2np.I2NPMessageImpl;
import net.i2p.router.RouterContext;
import net.i2p.router.util.CoDelBlockingQueue;
//import net.i2p.util.ByteCache;
import net.i2p.util.HexDump;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.SystemVersion;
/**
* Pull fully completed fragments off the {@link InboundMessageFragments} queue,
* parse 'em into I2NPMessages, and stick them on the
* {@link net.i2p.router.InNetMessagePool} by way of the {@link UDPTransport}.
*/
class MessageReceiver {
private final RouterContext _context;
private final Log _log;
private final UDPTransport _transport;
/** list of messages (InboundMessageState) fully received but not interpreted yet */
private final BlockingQueue<InboundMessageState> _completeMessages;
private volatile boolean _alive;
//private ByteCache _cache;
private static final int MIN_THREADS = 2; // unless < 32MB
private static final int MAX_THREADS = 5;
private static final int MIN_QUEUE_SIZE = 32; // unless < 32MB
private static final int MAX_QUEUE_SIZE = 128;
private final int _threadCount;
private static final long POISON_IMS = -99999999999l;
public MessageReceiver(RouterContext ctx, UDPTransport transport) {
_context = ctx;
_log = ctx.logManager().getLog(MessageReceiver.class);
_transport = transport;
long maxMemory = SystemVersion.getMaxMemory();
int qsize;
if (maxMemory < 32*1024*1024) {
_threadCount = 1;
qsize = 16;
} else if (maxMemory < 64*1024*1024) {
_threadCount = 2;
qsize = 32;
} else {
_threadCount = Math.max(MIN_THREADS, Math.min(MAX_THREADS, ctx.bandwidthLimiter().getInboundKBytesPerSecond() / 20));
qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (2*1024*1024)));
}
_completeMessages = new CoDelBlockingQueue<InboundMessageState>(ctx, "UDP-MessageReceiver", qsize);
// the runners run forever, no need to have a cache
//_cache = ByteCache.getInstance(64, I2NPMessage.MAX_SIZE);
_context.statManager().createRateStat("udp.inboundExpired", "How many messages were expired before reception?", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundRemaining", "How many messages were remaining when a message is pulled off the complete queue?", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundReady", "How many messages were ready when a message is added to the complete queue?", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundReadTime", "How long it takes to parse in the completed fragments into a message?", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundReceiveProcessTime", "How long it takes to add the message to the transport?", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.inboundLag", "How long the oldest ready message has been sitting on the queue (period is the queue size)?", "udp", UDPTransport.RATES);
_alive = true;
}
public synchronized void startup() {
_alive = true;
for (int i = 0; i < _threadCount; i++) {
I2PThread t = new I2PThread(new Runner(), "UDP message receiver " + (i+1) + '/' + _threadCount, true);
t.start();
}
}
private class Runner implements Runnable {
private final I2NPMessageHandler _handler;
public Runner() { _handler = new I2NPMessageHandler(_context); }
public void run() { loop(_handler); }
}
public synchronized void shutdown() {
_alive = false;
_completeMessages.clear();
for (int i = 0; i < _threadCount; i++) {
InboundMessageState ims = new InboundMessageState(_context, POISON_IMS, null);
_completeMessages.offer(ims);
}
for (int i = 1; i <= 5 && !_completeMessages.isEmpty(); i++) {
try {
Thread.sleep(i * 50);
} catch (InterruptedException ie) {}
}
_completeMessages.clear();
}
/**
* This queues the message for processing.
* Processing will call state.releaseResources(), do not access state after calling this.
* BLOCKING if queue is full.
*/
public void receiveMessage(InboundMessageState state) {
//int total = 0;
//long lag = -1;
if (_alive) {
try {
_completeMessages.put(state);
} catch (InterruptedException ie) {
_alive = false;
}
}
//total = _completeMessages.size();
//if (total > 1)
// lag = ((InboundMessageState)_completeMessages.get(0)).getLifetime();
//if (total > 1)
// _context.statManager().addRateData("udp.inboundReady", total, 0);
//if (lag > 1000)
// _context.statManager().addRateData("udp.inboundLag", lag, total);
}
public void loop(I2NPMessageHandler handler) {
InboundMessageState message = null;
//ByteArray buf = _cache.acquire();
ByteArray buf = new ByteArray(new byte[I2NPMessage.MAX_SIZE]);
while (_alive) {
int expired = 0;
long expiredLifetime = 0;
try {
while (message == null) {
message = _completeMessages.take();
if ( (message != null) && (message.getMessageId() == POISON_IMS) ) {
message = null;
break;
}
if ( (message != null) && (message.isExpired()) ) {
expiredLifetime += message.getLifetime();
// message.releaseResources() ??
message = null;
expired++;
}
//remaining = _completeMessages.size();
}
} catch (InterruptedException ie) {}
if (expired > 0)
_context.statManager().addRateData("udp.inboundExpired", expired, expiredLifetime);
if (message != null) {
//long before = System.currentTimeMillis();
//if (remaining > 0)
// _context.statManager().addRateData("udp.inboundRemaining", remaining, 0);
int size = message.getCompleteSize();
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Full message received (" + message.getMessageId() + ") after " + message.getLifetime());
//long afterRead = -1;
try {
I2NPMessage msg = readMessage(buf, message, handler);
//afterRead = System.currentTimeMillis();
if (msg != null)
_transport.messageReceived(msg, null, message.getFrom(), message.getLifetime(), size);
} catch (RuntimeException re) {
_log.error("b0rked receiving a message.. wazza huzza hmm?", re);
continue;
}
message = null;
//long after = System.currentTimeMillis();
//if (afterRead - before > 100)
// _context.statManager().addRateData("udp.inboundReadTime", afterRead - before, remaining);
//if (after - afterRead > 100)
// _context.statManager().addRateData("udp.inboundReceiveProcessTime", after - afterRead, remaining);
}
}
// no need to zero it out, as these buffers are only used with an explicit getCompleteSize
//_cache.release(buf, false);
}
/**
* Assemble all the fragments into an I2NP message.
* This calls state.releaseResources(), do not access state after calling this.
*
* @param buf temp buffer for convenience
* @return null on error
*/
private I2NPMessage readMessage(ByteArray buf, InboundMessageState state, I2NPMessageHandler handler) {
try {
//byte buf[] = new byte[state.getCompleteSize()];
I2NPMessage m;
int numFragments = state.getFragmentCount();
if (numFragments > 1) {
ByteArray fragments[] = state.getFragments();
int off = 0;
for (int i = 0; i < numFragments; i++) {
System.arraycopy(fragments[i].getData(), 0, buf.getData(), off, fragments[i].getValid());
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Raw fragment[" + i + "] for " + state.getMessageId() + ": "
// + Base64.encode(fragments[i].getData(), 0, fragments[i].getValid())
// + " (valid: " + fragments[i].getValid()
// + " raw: " + Base64.encode(fragments[i].getData()) + ")");
off += fragments[i].getValid();
}
if (off != state.getCompleteSize()) {
if (_log.shouldLog(Log.WARN))
_log.warn("Hmm, offset of the fragments = " + off + " while the state says " + state.getCompleteSize());
return null;
}
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Raw byte array for " + state.getMessageId() + ": " + HexDump.dump(buf.getData(), 0, state.getCompleteSize()));
m = I2NPMessageImpl.fromRawByteArray(_context, buf.getData(), 0, state.getCompleteSize(), handler);
} else {
// zero copy for single fragment
m = I2NPMessageImpl.fromRawByteArray(_context, state.getFragments()[0].getData(), 0, state.getCompleteSize(), handler);
}
m.setUniqueId(state.getMessageId());
return m;
} catch (I2NPMessageException ime) {
if (_log.shouldLog(Log.WARN)) {
ByteArray ba;
if (state.getFragmentCount() > 1)
ba = buf;
else
ba = state.getFragments()[0];
byte[] data = ba.getData();
_log.warn("Message invalid: " + state +
" PeerState: " + _transport.getPeerState(state.getFrom()) +
"\nDUMP:\n" + HexDump.dump(data, 0, state.getCompleteSize()) +
"\nRAW:\n" + Base64.encode(data, 0, state.getCompleteSize()),
ime);
}
if (state.getFragments()[0].getData()[0] == DatabaseStoreMessage.MESSAGE_TYPE) {
PeerState ps = _transport.getPeerState(state.getFrom());
if (ps != null && ps.getRemotePort() == 65520) {
// distinct port of buggy router
_transport.sendDestroy(ps);
_transport.dropPeer(ps, true, "Corrupt DSM");
_context.banlist().banlistRouterForever(state.getFrom(),
_x("Sent corrupt DSM"));
}
}
_context.messageHistory().droppedInboundMessage(state.getMessageId(), state.getFrom(), "error: " + ime.toString() + ": " + state.toString());
return null;
} catch (RuntimeException e) {
// e.g. AIOOBE
if (_log.shouldLog(Log.WARN))
_log.warn("Error handling a message: " + state, e);
_context.messageHistory().droppedInboundMessage(state.getMessageId(), state.getFrom(), "error: " + e.toString() + ": " + state.toString());
return null;
} finally {
state.releaseResources();
}
}
/**
* Mark a string for extraction by xgettext and translation.
* Use this only in static initializers.
* It does not translate!
* @return s
* @since 0.9.20
*/
private static final String _x(String s) {
return s;
}
}