package net.i2p.router.transport.ntcp;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Pool of running threads which will process any read bytes on any of the
* NTCPConnections, including the decryption of the data read, connection
* handshaking, parsing bytes into I2NP messages, etc.
*
*/
class Reader {
private final RouterContext _context;
private final Log _log;
// TODO change to LBQ ??
private final Set<NTCPConnection> _pendingConnections;
private final Set<NTCPConnection> _liveReads;
private final Set<NTCPConnection> _readAfterLive;
private final List<Runner> _runners;
public Reader(RouterContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(getClass());
_pendingConnections = new LinkedHashSet<NTCPConnection>(16);
_runners = new ArrayList<Runner>(8);
_liveReads = new HashSet<NTCPConnection>(8);
_readAfterLive = new HashSet<NTCPConnection>(8);
}
public synchronized void startReading(int numReaders) {
for (int i = 1; i <= numReaders; i++) {
Runner r = new Runner();
I2PThread t = new I2PThread(r, "NTCP reader " + i + '/' + numReaders, true);
_runners.add(r);
t.start();
}
}
public synchronized void stopReading() {
while (!_runners.isEmpty()) {
Runner r = _runners.remove(0);
r.stop();
}
synchronized (_pendingConnections) {
_readAfterLive.clear();
_pendingConnections.notifyAll();
}
}
public void wantsRead(NTCPConnection con) {
boolean already = false;
synchronized (_pendingConnections) {
if (_liveReads.contains(con)) {
_readAfterLive.add(con);
already = true;
} else {
_pendingConnections.add(con);
// only notify here if added?
}
_pendingConnections.notify();
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("wantsRead: " + con + " already live? " + already);
}
public void connectionClosed(NTCPConnection con) {
synchronized (_pendingConnections) {
_readAfterLive.remove(con);
_pendingConnections.remove(con);
// necessary?
_pendingConnections.notify();
}
}
private class Runner implements Runnable {
private volatile boolean _stop;
public Runner() {}
public void stop() { _stop = true; }
public void run() {
if (_log.shouldLog(Log.INFO)) _log.info("Starting reader");
NTCPConnection con = null;
while (!_stop) {
try {
synchronized (_pendingConnections) {
boolean keepReading = (con != null) && _readAfterLive.remove(con);
if (keepReading) {
// keep on reading the same one
} else {
if (con != null) {
_liveReads.remove(con);
con = null;
}
if (_pendingConnections.isEmpty()) {
_pendingConnections.wait();
} else {
Iterator<NTCPConnection> iter = _pendingConnections.iterator();
con = iter.next();
iter.remove();
_liveReads.add(con);
}
}
}
} catch (InterruptedException ie) {}
if (!_stop && (con != null) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("begin read for " + con);
try {
processRead(con);
} catch (IllegalStateException ise) {
// FailedEstablishState.receive() (race - see below)
if (_log.shouldWarn())
_log.warn("Error in the ntcp reader", ise);
} catch (RuntimeException re) {
_log.log(Log.CRIT, "Error in the ntcp reader", re);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("end read for " + con);
}
}
if (_log.shouldLog(Log.INFO)) _log.info("Stopping reader");
}
}
/**
* Process everything read.
* Return read buffers back to the pool as we process them.
*/
private void processRead(NTCPConnection con) {
ByteBuffer buf = null;
while(true) {
synchronized(con) {
if (con.isClosed())
return;
if (con.isEstablished())
break;
}
if ((buf = con.getNextReadBuf()) == null)
return;
EstablishState est = con.getEstablishState();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Processing read buffer as an establishment for " + con + " with [" + est + "]");
if (est.isComplete()) {
// why is it complete yet !con.isEstablished?
_log.error("establishment state [" + est + "] is complete, yet the connection isn't established? "
+ con.isEstablished() + " (inbound? " + con.isInbound() + " " + con + ")");
EventPumper.releaseBuf(buf);
break;
}
// FIXME call est.isCorrupt() before also? throws ISE here... see above
est.receive(buf);
EventPumper.releaseBuf(buf);
if (est.isCorrupt()) {
if (_log.shouldLog(Log.WARN))
_log.warn("closing connection on establishment because: " +est.getError(), est.getException());
if (!est.getFailedBySkew())
_context.statManager().addRateData("ntcp.receiveCorruptEstablishment", 1);
con.close();
return;
}
if (est.isComplete() && est.getExtraBytes() != null)
con.recvEncryptedI2NP(ByteBuffer.wrap(est.getExtraBytes()));
}
while (!con.isClosed() && (buf = con.getNextReadBuf()) != null) {
// decrypt the data and push it into an i2np message
if (_log.shouldLog(Log.DEBUG))
_log.debug("Processing read buffer as part of an i2np message (" + buf.remaining() + " bytes)");
con.recvEncryptedI2NP(buf);
EventPumper.releaseBuf(buf);
}
}
}