propagate from branch 'i2p.i2p' (head 3d405c867f6903bf1d69b04c1daebf3146882525)

to branch 'i2p.i2p.zzz.test4' (head bfd85b10fdd1542526a4b9c53e5d4a733087f317)
This commit is contained in:
zzz
2010-12-15 15:09:48 +00:00
72 changed files with 2147 additions and 736 deletions

View File

@ -3,6 +3,7 @@ package net.i2p;
import java.io.File;
import java.util.HashSet;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import net.i2p.client.naming.NamingService;
@ -21,7 +22,9 @@ import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.TransientSessionKeyManager;
import net.i2p.data.Base64;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.internal.InternalClientManager;
import net.i2p.stat.StatManager;
import net.i2p.util.Clock;
import net.i2p.util.ConcurrentHashSet;
@ -363,10 +366,12 @@ public class I2PAppContext {
if (_tmpDir == null) {
String d = getProperty("i2p.dir.temp", System.getProperty("java.io.tmpdir"));
// our random() probably isn't warmed up yet
String f = "i2p-" + Math.abs((new java.util.Random()).nextInt()) + ".tmp";
byte[] rand = new byte[6];
(new Random()).nextBytes(rand);
String f = "i2p-" + Base64.encode(rand) + ".tmp";
_tmpDir = new SecureDirectory(d, f);
if (_tmpDir.exists()) {
// good or bad ?
// good or bad ? loop and try again?
} else if (_tmpDir.mkdir()) {
_tmpDir.deleteOnExit();
} else {
@ -843,4 +848,13 @@ public class I2PAppContext {
public boolean isRouterContext() {
return false;
}
/**
* Use this to connect to the router in the same JVM.
* @return always null in I2PAppContext, the client manager if in RouterContext
* @since 0.8.3
*/
public InternalClientManager internalClientManager() {
return null;
}
}

View File

@ -9,6 +9,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageImpl;
import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.internal.PoisonI2CPMessage;
import net.i2p.util.I2PAppThread;
/**
@ -50,7 +51,7 @@ class ClientWriterRunner implements Runnable {
public void stopWriting() {
_messagesToWrite.clear();
try {
_messagesToWrite.put(new PoisonMessage());
_messagesToWrite.put(new PoisonI2CPMessage());
} catch (InterruptedException ie) {}
}
@ -62,7 +63,7 @@ class ClientWriterRunner implements Runnable {
} catch (InterruptedException ie) {
continue;
}
if (msg.getType() == PoisonMessage.MESSAGE_TYPE)
if (msg.getType() == PoisonI2CPMessage.MESSAGE_TYPE)
break;
// only thread, we don't need synchronized
try {
@ -80,18 +81,4 @@ class ClientWriterRunner implements Runnable {
}
_messagesToWrite.clear();
}
/**
* End-of-stream msg used to stop the concurrent queue
* See http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html
*
*/
private static class PoisonMessage extends I2CPMessageImpl {
public static final int MESSAGE_TYPE = 999999;
public int getType() {
return MESSAGE_TYPE;
}
public void doReadMessage(InputStream buf, int size) throws I2CPMessageException, IOException {}
public byte[] doWriteMessage() throws I2CPMessageException, IOException { return null; }
}
}

View File

@ -0,0 +1,183 @@
package net.i2p.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.security.KeyStore;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
/**
* Loads trusted ASCII certs from ~/.i2p/certificates/ and $CWD/certificates/.
* Keeps a single static SSLContext for the whole JVM.
*
* @author zzz
* @since 0.8.3
*/
class I2CPSSLSocketFactory {
private static final Object _initLock = new Object();
private static SSLSocketFactory _factory;
private static Log _log;
private static final String CERT_DIR = "certificates";
/**
* Initializes the static SSL Context if required, then returns a socket
* to the host.
*
* @param ctx just for logging
* @throws IOException on init error or usual socket errors
*/
public static Socket createSocket(I2PAppContext ctx, String host, int port) throws IOException {
synchronized(_initLock) {
if (_factory == null) {
_log = ctx.logManager().getLog(I2CPSSLSocketFactory.class);
initSSLContext(ctx);
if (_factory == null)
throw new IOException("Unable to create SSL Context for I2CP Client");
_log.info("I2CP Client-side SSL Context initialized");
}
}
return _factory.createSocket(host, port);
}
/**
* Loads certs from
* the ~/.i2p/certificates/ and $CWD/certificates/ directories.
*/
private static void initSSLContext(I2PAppContext context) {
KeyStore ks;
try {
ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, "".toCharArray());
} catch (GeneralSecurityException gse) {
_log.error("Key Store init error", gse);
return;
} catch (IOException ioe) {
_log.error("Key Store init error", ioe);
return;
}
File dir = new File(context.getConfigDir(), CERT_DIR);
int adds = addCerts(dir, ks);
int totalAdds = adds;
if (adds > 0 && _log.shouldLog(Log.INFO))
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
File dir2 = new File(System.getProperty("user.dir"), CERT_DIR);
if (!dir.getAbsolutePath().equals(dir2.getAbsolutePath())) {
adds = addCerts(dir2, ks);
totalAdds += adds;
if (adds > 0 && _log.shouldLog(Log.INFO))
_log.info("Loaded " + adds + " trusted certificates from " + dir.getAbsolutePath());
}
if (totalAdds > 0) {
if (_log.shouldLog(Log.INFO))
_log.info("Loaded total of " + totalAdds + " new trusted certificates");
} else {
_log.error("No trusted certificates loaded (looked in " +
dir.getAbsolutePath() + (dir.getAbsolutePath().equals(dir2.getAbsolutePath()) ? "" : (" and " + dir2.getAbsolutePath())) +
", I2CP SSL client connections will fail. " +
"Copy the file certificates/i2cp.local.crt from the router to the directory.");
// don't continue, since we didn't load the system keystore, we have nothing.
return;
}
try {
SSLContext sslc = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
sslc.init(null, tmf.getTrustManagers(), context.random());
_factory = sslc.getSocketFactory();
} catch (GeneralSecurityException gse) {
_log.error("SSL context init error", gse);
}
}
/**
* Load all X509 Certs from a directory and add them to the
* trusted set of certificates in the key store
*
* @return number successfully added
*/
private static int addCerts(File dir, KeyStore ks) {
if (_log.shouldLog(Log.INFO))
_log.info("Looking for X509 Certificates in " + dir.getAbsolutePath());
int added = 0;
if (dir.exists() && dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
File f = files[i];
if (!f.isFile())
continue;
// use file name as alias
String alias = f.getName().toLowerCase();
boolean success = addCert(f, alias, ks);
if (success)
added++;
}
}
}
return added;
}
/**
* Load an X509 Cert from a file and add it to the
* trusted set of certificates in the key store
*
* @return success
*/
private static boolean addCert(File file, String alias, KeyStore ks) {
InputStream fis = null;
try {
fis = new FileInputStream(file);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(fis);
if (_log.shouldLog(Log.INFO)) {
_log.info("Read X509 Certificate from " + file.getAbsolutePath() +
" Issuer: " + cert.getIssuerX500Principal() +
"; Valid From: " + cert.getNotBefore() +
" To: " + cert.getNotAfter());
}
try {
cert.checkValidity();
} catch (CertificateExpiredException cee) {
_log.error("Rejecting expired X509 Certificate: " + file.getAbsolutePath(), cee);
return false;
} catch (CertificateNotYetValidException cnyve) {
_log.error("Rejecting X509 Certificate not yet valid: " + file.getAbsolutePath(), cnyve);
return false;
}
ks.setCertificateEntry(alias, cert);
if (_log.shouldLog(Log.INFO))
_log.info("Now trusting X509 Certificate, Issuer: " + cert.getIssuerX500Principal());
} catch (GeneralSecurityException gse) {
_log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), gse);
return false;
} catch (IOException ioe) {
_log.error("Error reading X509 Certificate: " + file.getAbsolutePath(), ioe);
return false;
} finally {
try { if (fis != null) fis.close(); } catch (IOException foo) {}
}
return true;
}
}

View File

@ -39,8 +39,10 @@ import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.data.i2cp.I2CPMessageReader;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.data.i2cp.SessionId;
import net.i2p.util.I2PThread;
import net.i2p.util.InternalSocket;
import net.i2p.internal.I2CPMessageQueue;
import net.i2p.internal.InternalClientManager;
import net.i2p.internal.QueuedI2CPMessageReader;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
@ -66,9 +68,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
/** currently granted lease set, or null */
private LeaseSet _leaseSet;
/** hostname of router */
/** hostname of router - will be null if in RouterContext */
protected String _hostname;
/** port num to router */
/** port num to router - will be 0 if in RouterContext */
protected int _portNum;
/** socket for comm */
protected Socket _socket;
@ -79,6 +81,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
/** where we pipe our messages */
protected /* FIXME final FIXME */OutputStream _out;
/**
* Used for internal connections to the router.
* If this is set, _socket, _writer, and _out will be null.
* @since 0.8.3
*/
protected I2CPMessageQueue _queue;
/** who we send events to */
protected I2PSessionListener _sessionListener;
@ -122,6 +131,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
private long _lastActivity;
private boolean _isReduced;
/** SSL interface (only) @since 0.8.3 */
protected static final String PROP_ENABLE_SSL = "i2cp.SSL";
void dateUpdated() {
_dateReceived = true;
synchronized (_dateReceivedLock) {
@ -172,19 +184,24 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected void loadConfig(Properties options) {
_options = new Properties();
_options.putAll(filter(options));
_hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, LISTEN_PORT + "");
try {
_portNum = Integer.parseInt(portNum);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix() + "Invalid port number specified, defaulting to "
+ LISTEN_PORT, nfe);
_portNum = LISTEN_PORT;
if (_context.isRouterContext()) {
// just for logging
_hostname = "[internal connection]";
} else {
_hostname = _options.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
String portNum = _options.getProperty(I2PClient.PROP_TCP_PORT, LISTEN_PORT + "");
try {
_portNum = Integer.parseInt(portNum);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN))
_log.warn(getPrefix() + "Invalid port number specified, defaulting to "
+ LISTEN_PORT, nfe);
_portNum = LISTEN_PORT;
}
}
// auto-add auth if required, not set in the options, and we are in the same JVM
if (_context.isRouterContext() &&
// auto-add auth if required, not set in the options, and we are not in the same JVM
if ((!_context.isRouterContext()) &&
Boolean.valueOf(_context.getProperty("i2cp.auth")).booleanValue() &&
((!options.containsKey("i2cp.username")) || (!options.containsKey("i2cp.password")))) {
String configUser = _context.getProperty("i2cp.username");
@ -272,10 +289,6 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
setOpening(true);
_closed = false;
_availabilityNotifier.stopNotifying();
I2PThread notifier = new I2PThread(_availabilityNotifier);
notifier.setName("Notifier " + _myDestination.calculateHash().toBase64().substring(0,4));
notifier.setDaemon(true);
notifier.start();
if ( (_options != null) &&
(I2PClient.PROP_RELIABILITY_GUARANTEED.equals(_options.getProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT))) ) {
@ -285,17 +298,32 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
long startConnect = _context.clock().now();
try {
// If we are in the router JVM, connect using the interal pseudo-socket
_socket = InternalSocket.getSocket(_hostname, _portNum);
// _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it.
_out = _socket.getOutputStream();
synchronized (_out) {
_out.write(I2PClient.PROTOCOL_BYTE);
_out.flush();
// If we are in the router JVM, connect using the interal queue
if (_context.isRouterContext()) {
// _socket, _out, and _writer remain null
InternalClientManager mgr = _context.internalClientManager();
if (mgr == null)
throw new I2PSessionException("Router is not ready for connections");
// the following may throw an I2PSessionException
_queue = mgr.connect();
_reader = new QueuedI2CPMessageReader(_queue, this);
} else {
if (Boolean.valueOf(_options.getProperty(PROP_ENABLE_SSL)).booleanValue())
_socket = I2CPSSLSocketFactory.createSocket(_context, _hostname, _portNum);
else
_socket = new Socket(_hostname, _portNum);
// _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it.
_out = _socket.getOutputStream();
synchronized (_out) {
_out.write(I2PClient.PROTOCOL_BYTE);
_out.flush();
}
_writer = new ClientWriterRunner(_out, this);
InputStream in = _socket.getInputStream();
_reader = new I2CPMessageReader(in, this);
}
_writer = new ClientWriterRunner(_out, this);
InputStream in = _socket.getInputStream();
_reader = new I2CPMessageReader(in, this);
Thread notifier = new I2PAppThread(_availabilityNotifier, "ClientNotifier " + getPrefix(), true);
notifier.start();
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "before startReading");
_reader.startReading();
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before getDate");
@ -435,6 +463,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
}
/**
* This notifies the client of payload messages.
* Needs work.
*/
protected class AvailabilityNotifier implements Runnable {
private List _pendingIds;
private List _pendingSizes;
@ -497,8 +529,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
/**
* Recieve notification of some I2CP message and handle it if possible
*
* The I2CPMessageEventListener callback.
* Recieve notification of some I2CP message and handle it if possible.
* @param reader unused
*/
public void messageReceived(I2CPMessageReader reader, I2CPMessage message) {
I2CPMessageHandler handler = _handlerMap.getHandler(message.getType());
@ -515,7 +548,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
/**
* Recieve notifiation of an error reading the I2CP stream
* The I2CPMessageEventListener callback.
* Recieve notifiation of an error reading the I2CP stream.
* @param reader unused
* @param error non-null
*/
public void readError(I2CPMessageReader reader, Exception error) {
@ -567,9 +602,14 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
* @throws I2PSessionException if the message is malformed or there is an error writing it out
*/
void sendMessage(I2CPMessage message) throws I2PSessionException {
if (isClosed() || _writer == null)
if (isClosed())
throw new I2PSessionException("Already closed");
_writer.addMessage(message);
else if (_queue != null)
_queue.offer(message); // internal
else if (_writer == null)
throw new I2PSessionException("Already closed");
else
_writer.addMessage(message);
}
/**
@ -581,8 +621,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
// Only log as WARN if the router went away
int level;
String msgpfx;
if ((error instanceof EOFException) ||
(error.getMessage() != null && error.getMessage().startsWith("Pipe closed"))) {
if (error instanceof EOFException) {
level = Log.WARN;
msgpfx = "Router closed connection: ";
} else {
@ -631,7 +670,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_log.warn("Error destroying the session", ipe);
}
}
_availabilityNotifier.stopNotifying();
// SimpleSession does not initialize
if (_availabilityNotifier != null)
_availabilityNotifier.stopNotifying();
_closed = true;
_closing = false;
closeSocket();
@ -649,6 +690,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_reader.stopReading();
_reader = null;
}
if (_queue != null) {
// internal
_queue.close();
}
if (_writer != null) {
_writer.stopWriting();
_writer = null;
@ -666,7 +711,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
/**
* Recieve notification that the I2CP connection was disconnected
* The I2CPMessageEventListener callback.
* Recieve notification that the I2CP connection was disconnected.
* @param reader unused
*/
public void disconnected(I2CPMessageReader reader) {
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Disconnected", new Exception("Disconnected"));
@ -733,11 +780,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
buf.append(s);
else
buf.append(getClass().getSimpleName());
buf.append(" #");
if (_sessionId != null)
buf.append(_sessionId.getSessionId());
else
buf.append("n/a");
buf.append(" #").append(_sessionId.getSessionId());
buf.append("]: ");
return buf.toString();
}

View File

@ -19,8 +19,10 @@ import net.i2p.data.i2cp.DestLookupMessage;
import net.i2p.data.i2cp.DestReplyMessage;
import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
import net.i2p.data.i2cp.I2CPMessageReader;
import net.i2p.util.I2PThread;
import net.i2p.util.InternalSocket;
import net.i2p.internal.I2CPMessageQueue;
import net.i2p.internal.InternalClientManager;
import net.i2p.internal.QueuedI2CPMessageReader;
import net.i2p.util.I2PAppThread;
/**
* Create a new session for doing naming and bandwidth queries only. Do not create a Destination.
@ -44,12 +46,12 @@ class I2PSimpleSession extends I2PSessionImpl2 {
* @throws I2PSessionException if there is a problem
*/
public I2PSimpleSession(I2PAppContext context, Properties options) throws I2PSessionException {
// Warning, does not call super()
_context = context;
_log = context.logManager().getLog(I2PSimpleSession.class);
_handlerMap = new SimpleMessageHandlerMap(context);
_closed = true;
_closing = false;
_availabilityNotifier = new AvailabilityNotifier();
if (options == null)
options = System.getProperties();
loadConfig(options);
@ -65,23 +67,32 @@ class I2PSimpleSession extends I2PSessionImpl2 {
@Override
public void connect() throws I2PSessionException {
_closed = false;
_availabilityNotifier.stopNotifying();
I2PThread notifier = new I2PThread(_availabilityNotifier);
notifier.setName("Simple Notifier");
notifier.setDaemon(true);
notifier.start();
try {
// If we are in the router JVM, connect using the interal pseudo-socket
_socket = InternalSocket.getSocket(_hostname, _portNum);
_out = _socket.getOutputStream();
synchronized (_out) {
_out.write(I2PClient.PROTOCOL_BYTE);
_out.flush();
// If we are in the router JVM, connect using the interal queue
if (_context.isRouterContext()) {
// _socket, _out, and _writer remain null
InternalClientManager mgr = _context.internalClientManager();
if (mgr == null)
throw new I2PSessionException("Router is not ready for connections");
// the following may throw an I2PSessionException
_queue = mgr.connect();
_reader = new QueuedI2CPMessageReader(_queue, this);
} else {
if (Boolean.valueOf(getOptions().getProperty(PROP_ENABLE_SSL)).booleanValue())
_socket = I2CPSSLSocketFactory.createSocket(_context, _hostname, _portNum);
else
_socket = new Socket(_hostname, _portNum);
_out = _socket.getOutputStream();
synchronized (_out) {
_out.write(I2PClient.PROTOCOL_BYTE);
_out.flush();
}
_writer = new ClientWriterRunner(_out, this);
InputStream in = _socket.getInputStream();
_reader = new I2CPMessageReader(in, this);
}
_writer = new ClientWriterRunner(_out, this);
InputStream in = _socket.getInputStream();
_reader = new I2CPMessageReader(in, this);
// we do not receive payload messages, so we do not need an AvailabilityNotifier
_reader.startReading();
} catch (UnknownHostException uhe) {

View File

@ -27,11 +27,11 @@ import net.i2p.util.Log;
public class I2CPMessageReader {
private final static Log _log = new Log(I2CPMessageReader.class);
private InputStream _stream;
private I2CPMessageEventListener _listener;
private I2CPMessageReaderRunner _reader;
private Thread _readerThread;
protected I2CPMessageEventListener _listener;
protected I2CPMessageReaderRunner _reader;
protected Thread _readerThread;
private static volatile long __readerId = 0;
protected static volatile long __readerId = 0;
public I2CPMessageReader(InputStream stream, I2CPMessageEventListener lsnr) {
_stream = stream;
@ -42,6 +42,14 @@ public class I2CPMessageReader {
_readerThread.setName("I2CP Reader " + (++__readerId));
}
/**
* For internal extension only. No stream.
* @since 0.8.3
*/
protected I2CPMessageReader(I2CPMessageEventListener lsnr) {
setListener(lsnr);
}
public void setListener(I2CPMessageEventListener lsnr) {
_listener = lsnr;
}
@ -114,9 +122,9 @@ public class I2CPMessageReader {
public void disconnected(I2CPMessageReader reader);
}
private class I2CPMessageReaderRunner implements Runnable {
private volatile boolean _doRun;
private volatile boolean _stayAlive;
protected class I2CPMessageReaderRunner implements Runnable {
protected volatile boolean _doRun;
protected volatile boolean _stayAlive;
public I2CPMessageReaderRunner() {
_doRun = true;
@ -175,7 +183,8 @@ public class I2CPMessageReader {
cancelRunner();
}
}
if (!_doRun) {
// ??? unused
if (_stayAlive && !_doRun) {
// pause .5 secs when we're paused
try {
Thread.sleep(500);

View File

@ -0,0 +1,51 @@
package net.i2p.internal;
import net.i2p.data.i2cp.I2CPMessage;
/**
* Contains the methods to talk to a router or client via I2CP,
* when both are in the same JVM.
* This interface contains methods to access two queues,
* one for transmission and one for receiving.
* The methods are identical to those in java.util.concurrent.BlockingQueue.
*
* Reading may be done in a thread using the QueuedI2CPMessageReader class.
* Non-blocking writing may be done directly with offer().
*
* @author zzz
* @since 0.8.3
*/
public abstract class I2CPMessageQueue {
/**
* Send a message, nonblocking.
* @return success (false if no space available)
*/
public abstract boolean offer(I2CPMessage msg);
/**
* Receive a message, nonblocking.
* Unused for now.
* @return message or null if none available
*/
public abstract I2CPMessage poll();
/**
* Send a message, blocking until space is available.
* Unused for now.
*/
public abstract void put(I2CPMessage msg) throws InterruptedException;
/**
* Receive a message, blocking until one is available.
* @return message
*/
public abstract I2CPMessage take() throws InterruptedException;
/**
* == offer(new PoisonI2CPMessage());
*/
public void close() {
offer(new PoisonI2CPMessage());
}
}

View File

@ -0,0 +1,19 @@
package net.i2p.internal;
import net.i2p.client.I2PSessionException;
import net.i2p.data.i2cp.I2CPMessage;
/**
* A manager for the in-JVM I2CP message interface
*
* @author zzz
* @since 0.8.3
*/
public interface InternalClientManager {
/**
* Connect to the router, receiving a message queue to talk to the router with.
* @throws I2PSessionException if the router isn't ready
*/
public I2CPMessageQueue connect() throws I2PSessionException;
}

View File

@ -0,0 +1,56 @@
package net.i2p.internal;
import java.io.InputStream;
import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.data.i2cp.I2CPMessageImpl;
/**
* For marking end-of-queues in a standard manner.
* Don't actually send it.
*
* @author zzz
* @since 0.8.3
*/
public class PoisonI2CPMessage extends I2CPMessageImpl {
public final static int MESSAGE_TYPE = 999999;
public PoisonI2CPMessage() {
super();
}
/**
* @deprecated don't do this
* @throws I2CPMessageException always
*/
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException {
throw new I2CPMessageException("Don't do this");
}
/**
* @deprecated don't do this
* @throws I2CPMessageException always
*/
protected byte[] doWriteMessage() throws I2CPMessageException {
throw new I2CPMessageException("Don't do this");
}
public int getType() {
return MESSAGE_TYPE;
}
/* FIXME missing hashCode() method FIXME */
@Override
public boolean equals(Object object) {
if ((object != null) && (object instanceof PoisonI2CPMessage)) {
return true;
}
return false;
}
@Override
public String toString() {
return "[PoisonMessage]";
}
}

View File

@ -0,0 +1,62 @@
package net.i2p.internal;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageReader;
import net.i2p.util.I2PThread;
/**
* Get messages off an In-JVM queue, zero-copy
*
* @author zzz
* @since 0.8.3
*/
public class QueuedI2CPMessageReader extends I2CPMessageReader {
private final I2CPMessageQueue in;
public QueuedI2CPMessageReader(I2CPMessageQueue in, I2CPMessageEventListener lsnr) {
super(lsnr);
this.in = in;
_reader = new QueuedI2CPMessageReaderRunner();
_readerThread = new I2PThread(_reader, "I2CP Internal Reader " + (++__readerId), true);
}
protected class QueuedI2CPMessageReaderRunner extends I2CPMessageReaderRunner implements Runnable {
public QueuedI2CPMessageReaderRunner() {
super();
}
@Override
public void cancelRunner() {
super.cancelRunner();
_readerThread.interrupt();
}
@Override
public void run() {
while (_stayAlive) {
while (_doRun) {
// do read
I2CPMessage msg = null;
try {
msg = in.take();
if (msg.getType() == PoisonI2CPMessage.MESSAGE_TYPE)
cancelRunner();
else
_listener.messageReceived(QueuedI2CPMessageReader.this, msg);
} catch (InterruptedException ie) {}
}
// ??? unused
if (_stayAlive && !_doRun) {
// pause .5 secs when we're paused
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
_listener.disconnected(QueuedI2CPMessageReader.this);
cancelRunner();
}
}
}
}
}
}

View File

@ -0,0 +1,7 @@
<html><body>
<p>
Interface and classes for a router and client
within the same JVM to directly pass I2CP messages using Queues
instead of serialized messages over socket streams.
</p>
</body></html>

View File

@ -9,6 +9,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
@ -16,13 +18,11 @@ import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
// Pack200 import
// you must also uncomment the correct line in unpack() below
// For gcj, gij, etc., comment both out
// Pack200 now loaded dynamically in unpack() below
//
// For Sun, OpenJDK, IcedTea, etc, use this
import java.util.jar.Pack200;
//import java.util.jar.Pack200;
//
// For Apache Harmony or if you put its pack200.jar in your library directory use this
//import org.apache.harmony.unpack200.Archive;
@ -231,37 +231,79 @@ public class FileUtil {
}
/**
* This won't work right if one of the two options in unpack() is commented out.
* Public since 0.8.3
* @since 0.8.1
*/
private static boolean isPack200Supported() {
public static boolean isPack200Supported() {
try {
Class.forName("java.util.jar.Pack200", false, ClassLoader.getSystemClassLoader());
return true;
} catch (Exception e) {}
try {
Class.forName("org.apache.harmony.pack200.Archive", false, ClassLoader.getSystemClassLoader());
Class.forName("org.apache.harmony.unpack200.Archive", false, ClassLoader.getSystemClassLoader());
return true;
} catch (Exception e) {}
return false;
}
private static boolean _failedOracle;
private static boolean _failedApache;
/**
* Unpack using either Oracle or Apache's unpack200 library,
* with the classes discovered at runtime so neither is required at compile time.
*
* Caller must close streams
* @throws IOException on unpack error or if neither library is available.
* Will not throw ClassNotFoundException.
* @throws org.apache.harmony.pack200.Pack200Exception which is not an IOException
* @since 0.8.1
*/
private static void unpack(InputStream in, JarOutputStream out) throws Exception {
// For Sun, OpenJDK, IcedTea, etc, use this
Pack200.newUnpacker().unpack(in, out);
//Pack200.newUnpacker().unpack(in, out);
if (!_failedOracle) {
try {
Class p200 = Class.forName("java.util.jar.Pack200", true, ClassLoader.getSystemClassLoader());
Method newUnpacker = p200.getMethod("newUnpacker", (Class[]) null);
Object unpacker = newUnpacker.invoke(null,(Object[]) null);
Method unpack = unpacker.getClass().getMethod("unpack", new Class[] {InputStream.class, JarOutputStream.class});
// throws IOException
unpack.invoke(unpacker, new Object[] {in, out});
return;
} catch (ClassNotFoundException e) {
_failedOracle = true;
//e.printStackTrace();
} catch (NoSuchMethodException e) {
_failedOracle = true;
//e.printStackTrace();
}
}
// ------------------
// For Apache Harmony or if you put its pack200.jar in your library directory use this
//(new Archive(in, out)).unpack();
if (!_failedApache) {
try {
Class p200 = Class.forName("org.apache.harmony.unpack200.Archive", true, ClassLoader.getSystemClassLoader());
Constructor newUnpacker = p200.getConstructor(new Class[] {InputStream.class, JarOutputStream.class});
Object unpacker = newUnpacker.newInstance(new Object[] {in, out});
Method unpack = unpacker.getClass().getMethod("unpack", (Class[]) null);
// throws IOException or Pack200Exception
unpack.invoke(unpacker, (Object[]) null);
return;
} catch (ClassNotFoundException e) {
_failedApache = true;
//e.printStackTrace();
} catch (NoSuchMethodException e) {
_failedApache = true;
//e.printStackTrace();
}
}
// ------------------
// For gcj, gij, etc., use this
//throw new IOException("Pack200 not supported");
throw new IOException("Unpack200 not supported");
}
/**
@ -378,12 +420,13 @@ public class FileUtil {
}
/**
* Usage: FileUtil (delete path | copy source dest)
* Usage: FileUtil (delete path | copy source dest | unzip path.zip)
*
*/
public static void main(String args[]) {
if ( (args == null) || (args.length < 2) ) {
testRmdir();
System.err.println("Usage: delete path | copy source dest | unzip path.zip");
//testRmdir();
} else if ("delete".equals(args[0])) {
boolean deleted = FileUtil.rmdir(args[1], false);
if (!deleted)
@ -407,6 +450,7 @@ public class FileUtil {
}
}
/*****
private static void testRmdir() {
File t = new File("rmdirTest/test/subdir/blah");
boolean created = t.mkdirs();
@ -417,4 +461,5 @@ public class FileUtil {
else
System.out.println("PASS: rmdirTest deleted");
}
*****/
}

View File

@ -61,8 +61,8 @@ public class I2PThread extends Thread {
_createdBy = new Exception("Created by");
}
private void log(int level, String msg) { log(level, msg, null); }
private void log(int level, String msg, Throwable t) {
private static void log(int level, String msg) { log(level, msg, null); }
private static void log(int level, String msg, Throwable t) {
// we cant assume log is created
if (_log == null) _log = new Log(I2PThread.class);
if (_log.shouldLog(level))
@ -72,12 +72,12 @@ public class I2PThread extends Thread {
@Override
public void run() {
_name = Thread.currentThread().getName();
log(Log.DEBUG, "New thread started: " + _name, _createdBy);
log(Log.INFO, "New thread started" + (isDaemon() ? " (daemon): " : ": ") + _name, _createdBy);
try {
super.run();
} catch (Throwable t) {
try {
log(Log.CRIT, "Killing thread " + getName(), t);
log(Log.CRIT, "Thread terminated unexpectedly: " + getName(), t);
} catch (Throwable woof) {
System.err.println("Died within the OOM itself");
t.printStackTrace();
@ -85,12 +85,12 @@ public class I2PThread extends Thread {
if (t instanceof OutOfMemoryError)
fireOOM((OutOfMemoryError)t);
}
log(Log.DEBUG, "Thread finished gracefully: " + _name);
log(Log.INFO, "Thread finished normally: " + _name);
}
@Override
protected void finalize() throws Throwable {
log(Log.DEBUG, "Thread finalized: " + _name);
//log(Log.DEBUG, "Thread finalized: " + _name);
super.finalize();
}

View File

@ -51,17 +51,18 @@ public class ShellCommand {
*/
private class CommandThread extends Thread {
final Object caller;
boolean consumeOutput;
String shellCommand;
private final Object caller;
private final boolean consumeOutput;
private final Object shellCommand;
CommandThread(Object caller, String shellCommand, boolean consumeOutput) {
/**
* @param shellCommand either a String or a String[] (since 0.8.3)
*/
CommandThread(Object caller, Object shellCommand, boolean consumeOutput) {
super("CommandThread");
this.caller = caller;
this.shellCommand = shellCommand;
this.consumeOutput = consumeOutput;
_commandSuccessful = false;
_commandCompleted = false;
}
@Override
@ -200,6 +201,9 @@ public class ShellCommand {
* {@link #getErrorStream()}, respectively. Input can be passed to the
* <code>STDIN</code> of the shell process via {@link #getInputStream()}.
*
* Warning, no good way to quote or escape spaces in arguments with this method.
* @deprecated unused
*
* @param shellCommand The command for the shell to execute.
*/
public void execute(String shellCommand) {
@ -215,6 +219,9 @@ public class ShellCommand {
* Input can be passed to the <code>STDIN</code> of the shell process via
* {@link #getInputStream()}.
*
* Warning, no good way to quote or escape spaces in arguments with this method.
* @deprecated unused
*
* @param shellCommand The command for the shell to execute.
* @return <code>true</code> if the spawned shell process
* returns an exit status of 0 (indicating success),
@ -237,6 +244,9 @@ public class ShellCommand {
* {@link #getErrorStream()}, respectively. Input can be passed to the
* <code>STDIN</code> of the shell process via {@link #getInputStream()}.
*
* Warning, no good way to quote or escape spaces in arguments with this method.
* @deprecated unused
*
* @param shellCommand The command for the shell to execute.
* @param seconds The method will return <code>true</code> if this
* number of seconds elapses without the process
@ -276,6 +286,9 @@ public class ShellCommand {
* without waiting for an exit status. Any output produced by the executed
* command will not be displayed.
*
* Warning, no good way to quote or escape spaces in arguments with this method.
* @deprecated unused
*
* @param shellCommand The command for the shell to execute.
* @throws IOException
*/
@ -288,6 +301,8 @@ public class ShellCommand {
* all of the command's resulting shell processes have completed. Any output
* produced by the executed command will not be displayed.
*
* Warning, no good way to quote or escape spaces in arguments with this method.
*
* @param shellCommand The command for the shell to execute.
* @return <code>true</code> if the spawned shell process
* returns an exit status of 0 (indicating success),
@ -307,7 +322,12 @@ public class ShellCommand {
* specified number of seconds has elapsed first. Any output produced by the
* executed command will not be displayed.
*
* @param shellCommand The command for the shell to execute.
* Warning, no good way to quote or escape spaces in arguments when shellCommand is a String.
* Use a String array for best results, especially on Windows.
*
* @param shellCommand The command for the shell to execute, as a String.
* You can't quote arguments successfully.
* See Runtime.exec(String) for more info.
* @param seconds The method will return <code>true</code> if this
* number of seconds elapses without the process
* returning an exit status. A value of <code>0</code>
@ -317,7 +337,33 @@ public class ShellCommand {
* else <code>false</code>.
*/
public synchronized boolean executeSilentAndWaitTimed(String shellCommand, int seconds) {
return executeSAWT(shellCommand, seconds);
}
/**
* Passes a command to the shell for execution. This method blocks until
* all of the command's resulting shell processes have completed unless a
* specified number of seconds has elapsed first. Any output produced by the
* executed command will not be displayed.
*
* @param commandArray The command for the shell to execute,
* as a String[].
* See Runtime.exec(String[]) for more info.
* @param seconds The method will return <code>true</code> if this
* number of seconds elapses without the process
* returning an exit status. A value of <code>0</code>
* here disables waiting.
* @return <code>true</code> if the spawned shell process
* returns an exit status of 0 (indicating success),
* else <code>false</code>.
* @since 0.8.3
*/
public synchronized boolean executeSilentAndWaitTimed(String[] commandArray, int seconds) {
return executeSAWT(commandArray, seconds);
}
/** @since 0.8.3 */
private boolean executeSAWT(Object shellCommand, int seconds) {
_commandThread = new CommandThread(this, shellCommand, CONSUME_OUTPUT);
_commandThread.start();
try {
@ -364,7 +410,10 @@ public class ShellCommand {
return;
}
private boolean execute(String shellCommand, boolean consumeOutput, boolean waitForExitStatus) {
/**
* @param shellCommand either a String or a String[] (since 0.8.3) - quick hack
*/
private boolean execute(Object shellCommand, boolean consumeOutput, boolean waitForExitStatus) {
StreamConsumer processStderrConsumer;
StreamConsumer processStdoutConsumer;
@ -374,7 +423,13 @@ public class ShellCommand {
StreamReader processStdoutReader;
try {
_process = Runtime.getRuntime().exec(shellCommand, null);
// easy way so we don't have to copy this whole method
if (shellCommand instanceof String)
_process = Runtime.getRuntime().exec((String)shellCommand);
else if (shellCommand instanceof String[])
_process = Runtime.getRuntime().exec((String[])shellCommand);
else
throw new ClassCastException("shell command must be a String or a String[]");
if (consumeOutput) {
processStderrConsumer = new StreamConsumer(_process.getErrorStream());
processStderrConsumer.start();

View File

@ -28,12 +28,14 @@ import net.i2p.I2PAppContext;
public class SimpleScheduler {
private static final SimpleScheduler _instance = new SimpleScheduler();
public static SimpleScheduler getInstance() { return _instance; }
private static final int THREADS = 4;
private static final int MIN_THREADS = 2;
private static final int MAX_THREADS = 4;
private I2PAppContext _context;
private Log _log;
private ScheduledThreadPoolExecutor _executor;
private String _name;
private int _count;
private final int _threads;
protected SimpleScheduler() { this("SimpleScheduler"); }
protected SimpleScheduler(String name) {
@ -41,7 +43,9 @@ public class SimpleScheduler {
_log = _context.logManager().getLog(SimpleScheduler.class);
_name = name;
_count = 0;
_executor = new ScheduledThreadPoolExecutor(THREADS, new CustomThreadFactory());
long maxMemory = Runtime.getRuntime().maxMemory();
_threads = (int) Math.max(MIN_THREADS, Math.min(MAX_THREADS, 1 + (maxMemory / (32*1024*1024))));
_executor = new ScheduledThreadPoolExecutor(_threads, new CustomThreadFactory());
_executor.prestartAllCoreThreads();
}
@ -65,6 +69,13 @@ public class SimpleScheduler {
re.schedule();
}
/**
* Queue up the given event to be fired after timeoutMs and every
* timeoutMs thereafter. The TimedEvent must not do its own rescheduling.
* As all Exceptions are caught in run(), these will not prevent
* subsequent executions (unlike SimpleTimer, where the TimedEvent does
* its own rescheduling).
*/
public void addPeriodicEvent(SimpleTimer.TimedEvent event, long timeoutMs) {
addPeriodicEvent(event, timeoutMs, timeoutMs);
}
@ -90,7 +101,7 @@ public class SimpleScheduler {
private class CustomThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread rv = Executors.defaultThreadFactory().newThread(r);
rv.setName(_name + ' ' + (++_count) + '/' + THREADS);
rv.setName(_name + ' ' + (++_count) + '/' + _threads);
// Uncomment this to test threadgrouping, but we should be all safe now that the constructor preallocates!
// String name = rv.getThreadGroup().getName();
// if(!name.equals("main")) {

View File

@ -18,14 +18,16 @@ import net.i2p.I2PAppContext;
public class SimpleTimer {
private static final SimpleTimer _instance = new SimpleTimer();
public static SimpleTimer getInstance() { return _instance; }
private I2PAppContext _context;
private Log _log;
private final I2PAppContext _context;
private final Log _log;
/** event time (Long) to event (TimedEvent) mapping */
private final TreeMap _events;
/** event (TimedEvent) to event time (Long) mapping */
private Map _eventTimes;
private final List _readyEvents;
private SimpleStore runn;
private static final int MIN_THREADS = 2;
private static final int MAX_THREADS = 4;
protected SimpleTimer() { this("SimpleTimer"); }
protected SimpleTimer(String name) {
@ -39,9 +41,11 @@ public class SimpleTimer {
runner.setName(name);
runner.setDaemon(true);
runner.start();
for (int i = 0; i < 3; i++) {
long maxMemory = Runtime.getRuntime().maxMemory();
int threads = (int) Math.max(MIN_THREADS, Math.min(MAX_THREADS, 1 + (maxMemory / (32*1024*1024))));
for (int i = 1; i <= threads ; i++) {
I2PThread executor = new I2PThread(new Executor(_context, _log, _readyEvents, runn));
executor.setName(name + "Executor " + i);
executor.setName(name + "Executor " + i + '/' + threads);
executor.setDaemon(true);
executor.start();
}

View File

@ -27,12 +27,14 @@ import net.i2p.I2PAppContext;
public class SimpleTimer2 {
private static final SimpleTimer2 _instance = new SimpleTimer2();
public static SimpleTimer2 getInstance() { return _instance; }
private static final int THREADS = 4;
private static final int MIN_THREADS = 2;
private static final int MAX_THREADS = 4;
private I2PAppContext _context;
private static Log _log; // static so TimedEvent can use it
private ScheduledThreadPoolExecutor _executor;
private String _name;
private int _count;
private final int _threads;
protected SimpleTimer2() { this("SimpleTimer2"); }
protected SimpleTimer2(String name) {
@ -40,7 +42,9 @@ public class SimpleTimer2 {
_log = _context.logManager().getLog(SimpleTimer2.class);
_name = name;
_count = 0;
_executor = new CustomScheduledThreadPoolExecutor(THREADS, new CustomThreadFactory());
long maxMemory = Runtime.getRuntime().maxMemory();
_threads = (int) Math.max(MIN_THREADS, Math.min(MAX_THREADS, 1 + (maxMemory / (32*1024*1024))));
_executor = new CustomScheduledThreadPoolExecutor(_threads, new CustomThreadFactory());
_executor.prestartAllCoreThreads();
}
@ -67,7 +71,7 @@ public class SimpleTimer2 {
private class CustomThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread rv = Executors.defaultThreadFactory().newThread(r);
rv.setName(_name + ' ' + (++_count) + '/' + THREADS);
rv.setName(_name + ' ' + (++_count) + '/' + _threads);
// Uncomment this to test threadgrouping, but we should be all safe now that the constructor preallocates!
// String name = rv.getThreadGroup().getName();
// if(!name.equals("main")) {