mirror of
https://github.com/i2p/i2p.i2p.git
synced 2024-12-06 19:27:00 +01:00
propagate from branch 'i2p.i2p.0971' (head 6cb629b61e0177deda1e539b2f85a2473f3af7fb)
to branch 'i2p.i2p' (head 8e86ef70afbbbbdc2db06cec34f17dedd528c7e7)
This commit is contained in:
@@ -139,13 +139,18 @@ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnabl
|
||||
long before = System.currentTimeMillis();
|
||||
doFill(aBuff.buffer);
|
||||
long after = System.currentTimeMillis();
|
||||
boolean shouldWait = _fullBuffers.size() > 1;
|
||||
_fullBuffers.offer(aBuff);
|
||||
_context.statManager().addRateData("prng.bufferFillTime", after - before, 0);
|
||||
Thread.yield();
|
||||
long waitTime = (after-before)*5;
|
||||
if (waitTime <= 0) // somehow postman saw waitTime show up as negative
|
||||
waitTime = 50;
|
||||
try { Thread.sleep(waitTime); } catch (InterruptedException ie) {}
|
||||
if (shouldWait) {
|
||||
Thread.yield();
|
||||
long waitTime = (after-before)*5;
|
||||
if (waitTime <= 0) // somehow postman saw waitTime show up as negative
|
||||
waitTime = 50;
|
||||
else if (waitTime > 5000)
|
||||
waitTime = 5000;
|
||||
try { Thread.sleep(waitTime); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,12 +80,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
protected I2CPMessageReader _reader;
|
||||
/** writer message queue */
|
||||
protected ClientWriterRunner _writer;
|
||||
/** 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.
|
||||
* If this is set, _socket and _writer will be null.
|
||||
* @since 0.8.3
|
||||
*/
|
||||
protected I2CPMessageQueue _queue;
|
||||
@@ -94,7 +92,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
protected I2PSessionListener _sessionListener;
|
||||
|
||||
/** class that generates new messages */
|
||||
protected I2CPMessageProducer _producer;
|
||||
protected final I2CPMessageProducer _producer;
|
||||
/** map of Long --> MessagePayloadMessage */
|
||||
protected Map<Long, MessagePayloadMessage> _availableMessages;
|
||||
|
||||
@@ -103,7 +101,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
protected final Object _bwReceivedLock = new Object();
|
||||
protected volatile int[] _bwLimits;
|
||||
|
||||
protected I2PClientMessageHandlerMap _handlerMap;
|
||||
protected final I2PClientMessageHandlerMap _handlerMap;
|
||||
|
||||
/** used to seperate things out so we can get rid of singletons */
|
||||
protected final I2PAppContext _context;
|
||||
@@ -111,22 +109,24 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
/** monitor for waiting until a lease set has been granted */
|
||||
private final Object _leaseSetWait = new Object();
|
||||
|
||||
/** whether the session connection has already been closed (or not yet opened) */
|
||||
protected volatile boolean _closed;
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
protected enum State {
|
||||
OPENING,
|
||||
OPEN,
|
||||
CLOSING,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
/** whether the session connection is in the process of being closed */
|
||||
protected volatile boolean _closing;
|
||||
private State _state = State.CLOSED;
|
||||
protected final Object _stateLock = new Object();
|
||||
|
||||
/** have we received the current date from the router yet? */
|
||||
private volatile boolean _dateReceived;
|
||||
/** lock that we wait upon, that the SetDateMessageHandler notifies */
|
||||
private final Object _dateReceivedLock = new Object();
|
||||
|
||||
/** whether the session connection is in the process of being opened */
|
||||
protected volatile boolean _opening;
|
||||
|
||||
/** monitor for waiting until opened */
|
||||
private final Object _openingWait = new Object();
|
||||
/**
|
||||
* thread that we tell when new messages are available who then tells us
|
||||
* to fetch them. The point of this is so that the fetch doesn't block the
|
||||
@@ -168,22 +168,24 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
public static final int LISTEN_PORT = 7654;
|
||||
|
||||
private static final int BUF_SIZE = 32*1024;
|
||||
|
||||
|
||||
/**
|
||||
* for extension by SimpleSession (no dest)
|
||||
*/
|
||||
protected I2PSessionImpl(I2PAppContext context, Properties options) {
|
||||
this(context, options, false);
|
||||
protected I2PSessionImpl(I2PAppContext context, Properties options,
|
||||
I2PClientMessageHandlerMap handlerMap) {
|
||||
this(context, options, handlerMap, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic setup of finals
|
||||
* @since 0.9.7
|
||||
*/
|
||||
private I2PSessionImpl(I2PAppContext context, Properties options, boolean hasDest) {
|
||||
private I2PSessionImpl(I2PAppContext context, Properties options,
|
||||
I2PClientMessageHandlerMap handlerMap, boolean hasDest) {
|
||||
_context = context;
|
||||
_handlerMap = handlerMap;
|
||||
_log = context.logManager().getLog(getClass());
|
||||
_closed = true;
|
||||
if (options == null)
|
||||
options = (Properties) System.getProperties().clone();
|
||||
_options = loadConfig(options);
|
||||
@@ -191,10 +193,14 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
_portNum = getPort();
|
||||
_fastReceive = Boolean.parseBoolean(_options.getProperty(I2PClient.PROP_FAST_RECEIVE));
|
||||
if (hasDest) {
|
||||
_producer = new I2CPMessageProducer(context);
|
||||
_availableMessages = new ConcurrentHashMap();
|
||||
_myDestination = new Destination();
|
||||
_privateKey = new PrivateKey();
|
||||
_signingPrivateKey = new SigningPrivateKey();
|
||||
} else {
|
||||
_producer = null;
|
||||
_availableMessages = null;
|
||||
_myDestination = null;
|
||||
_privateKey = null;
|
||||
_signingPrivateKey = null;
|
||||
@@ -211,11 +217,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
* @throws I2PSessionException if there is a problem loading the private keys or
|
||||
*/
|
||||
public I2PSessionImpl(I2PAppContext context, InputStream destKeyStream, Properties options) throws I2PSessionException {
|
||||
this(context, options, true);
|
||||
_handlerMap = new I2PClientMessageHandlerMap(context);
|
||||
_producer = new I2CPMessageProducer(context);
|
||||
this(context, options, new I2PClientMessageHandlerMap(context), true);
|
||||
_availabilityNotifier = new AvailabilityNotifier();
|
||||
_availableMessages = new ConcurrentHashMap();
|
||||
try {
|
||||
readDestination(destKeyStream);
|
||||
} catch (DataFormatException dfe) {
|
||||
@@ -306,11 +309,14 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
continue;
|
||||
}
|
||||
String val = options.getProperty(key);
|
||||
if ((key.length() > 255) || (val.length() > 255)) {
|
||||
// Long strings MUST be removed, even in router context,
|
||||
// as the session config properties must be serialized to be signed.
|
||||
// fixme, bytes could still be over 255 (unlikely)
|
||||
if (key.length() > 255 || val.length() > 255) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix() + "Not passing on property ["
|
||||
_log.warn("Not passing on property ["
|
||||
+ key
|
||||
+ "] in the session configuration as the value is too long (max = 255): "
|
||||
+ "] in the session config, key or value is too long (max = 255): "
|
||||
+ val);
|
||||
} else {
|
||||
rv.setProperty(key, val);
|
||||
@@ -351,17 +357,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
return _leaseSet;
|
||||
}
|
||||
|
||||
void setOpening(boolean ls) {
|
||||
_opening = ls;
|
||||
synchronized (_openingWait) {
|
||||
_openingWait.notifyAll();
|
||||
protected void changeState(State state) {
|
||||
synchronized (_stateLock) {
|
||||
_state = state;
|
||||
_stateLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
boolean getOpening() {
|
||||
return _opening;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load up the destKeyFile for our Destination, PrivateKey, and SigningPrivateKey
|
||||
*
|
||||
@@ -378,12 +380,41 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
* Connect to the router and establish a session. This call blocks until
|
||||
* a session is granted.
|
||||
*
|
||||
* Should be threadsafe, other threads will block until complete.
|
||||
* Disconnect / destroy from another thread may be called simultaneously and
|
||||
* will (should?) interrupt the connect.
|
||||
*
|
||||
* @throws I2PSessionException if there is a configuration error or the router is
|
||||
* not reachable
|
||||
*/
|
||||
public void connect() throws I2PSessionException {
|
||||
setOpening(true);
|
||||
_closed = false;
|
||||
synchronized(_stateLock) {
|
||||
boolean wasOpening = false;
|
||||
boolean loop = true;
|
||||
while (loop) {
|
||||
switch (_state) {
|
||||
case CLOSED:
|
||||
if (wasOpening)
|
||||
throw new I2PSessionException("connect by other thread failed");
|
||||
loop = false;
|
||||
break;
|
||||
case OPENING:
|
||||
wasOpening = true;
|
||||
try {
|
||||
_stateLock.wait(10*1000);
|
||||
} catch (InterruptedException ie) {
|
||||
throw new I2PSessionException("Interrupted", ie);
|
||||
}
|
||||
break;
|
||||
case CLOSING:
|
||||
throw new I2PSessionException("close in progress");
|
||||
case OPEN:
|
||||
return;
|
||||
}
|
||||
}
|
||||
changeState(State.OPENING);
|
||||
}
|
||||
|
||||
_availabilityNotifier.stopNotifying();
|
||||
|
||||
if ( (_options != null) &&
|
||||
@@ -392,32 +423,34 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
_log.error("I2CP guaranteed delivery mode has been removed, using best effort.");
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
long startConnect = _context.clock().now();
|
||||
try {
|
||||
// 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.parseBoolean(_options.getProperty(PROP_ENABLE_SSL)))
|
||||
_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();
|
||||
_out.write(I2PClient.PROTOCOL_BYTE);
|
||||
_out.flush();
|
||||
_writer = new ClientWriterRunner(_out, this);
|
||||
InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE);
|
||||
_reader = new I2CPMessageReader(in, this);
|
||||
// protect w/ closeSocket()
|
||||
synchronized(_stateLock) {
|
||||
// If we are in the router JVM, connect using the interal queue
|
||||
if (_context.isRouterContext()) {
|
||||
// _socket 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.parseBoolean(_options.getProperty(PROP_ENABLE_SSL)))
|
||||
_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.
|
||||
OutputStream out = _socket.getOutputStream();
|
||||
out.write(I2PClient.PROTOCOL_BYTE);
|
||||
out.flush();
|
||||
_writer = new ClientWriterRunner(out, this);
|
||||
InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE);
|
||||
_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");
|
||||
@@ -426,55 +459,60 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
int waitcount = 0;
|
||||
while (!_dateReceived) {
|
||||
if (waitcount++ > 30) {
|
||||
closeSocket();
|
||||
throw new IOException("No handshake received from the router");
|
||||
}
|
||||
try {
|
||||
synchronized (_dateReceivedLock) {
|
||||
_dateReceivedLock.wait(1000);
|
||||
}
|
||||
} catch (InterruptedException ie) { // nop
|
||||
synchronized (_dateReceivedLock) {
|
||||
// InterruptedException caught below
|
||||
_dateReceivedLock.wait(1000);
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After received a SetDate response");
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before producer.connect()");
|
||||
_producer.connect(this);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After producer.connect()");
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After producer.connect()");
|
||||
|
||||
// wait until we have created a lease set
|
||||
waitcount = 0;
|
||||
while (_leaseSet == null) {
|
||||
if (waitcount++ > 5*60) {
|
||||
throw new IOException("No tunnels built after waiting 5 minutes. Your network connection may be down, or there is severe network congestion.");
|
||||
}
|
||||
synchronized (_leaseSetWait) {
|
||||
// InterruptedException caught below
|
||||
_leaseSetWait.wait(1000);
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
long connected = _context.clock().now();
|
||||
_log.info(getPrefix() + "Lease set created with inbound tunnels after "
|
||||
+ (connected - startConnect)
|
||||
+ "ms - ready to participate in the network!");
|
||||
}
|
||||
Thread notifier = new I2PAppThread(_availabilityNotifier, "ClientNotifier " + getPrefix(), true);
|
||||
notifier.start();
|
||||
startIdleMonitor();
|
||||
startVerifyUsage();
|
||||
success = true;
|
||||
} catch (InterruptedException ie) {
|
||||
throw new I2PSessionException("Interrupted", ie);
|
||||
} catch (UnknownHostException uhe) {
|
||||
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, uhe);
|
||||
} catch (IOException ioe) {
|
||||
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, ioe);
|
||||
} finally {
|
||||
if (success) {
|
||||
changeState(State.OPEN);
|
||||
} else {
|
||||
_availabilityNotifier.stopNotifying();
|
||||
synchronized(_stateLock) {
|
||||
changeState(State.CLOSING);
|
||||
try {
|
||||
_producer.disconnect(this);
|
||||
} catch (I2PSessionException ipe) {}
|
||||
closeSocket();
|
||||
throw new IOException("No tunnels built after waiting 5 minutes. Your network connection may be down, or there is severe network congestion.");
|
||||
}
|
||||
synchronized (_leaseSetWait) {
|
||||
try {
|
||||
_leaseSetWait.wait(1000);
|
||||
} catch (InterruptedException ie) { // nop
|
||||
}
|
||||
}
|
||||
}
|
||||
long connected = _context.clock().now();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix() + "Lease set created with inbound tunnels after "
|
||||
+ (connected - startConnect)
|
||||
+ "ms - ready to participate in the network!");
|
||||
startIdleMonitor();
|
||||
startVerifyUsage();
|
||||
setOpening(false);
|
||||
} catch (UnknownHostException uhe) {
|
||||
_closed = true;
|
||||
setOpening(false);
|
||||
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, uhe);
|
||||
} catch (IOException ioe) {
|
||||
_closed = true;
|
||||
setOpening(false);
|
||||
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,8 +608,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
* Needs work.
|
||||
*/
|
||||
protected class AvailabilityNotifier implements Runnable {
|
||||
private final List _pendingIds;
|
||||
private final List _pendingSizes;
|
||||
private final List<Long> _pendingIds;
|
||||
private final List<Integer> _pendingSizes;
|
||||
private volatile boolean _alive;
|
||||
|
||||
public AvailabilityNotifier() {
|
||||
@@ -606,8 +644,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
}
|
||||
}
|
||||
if (!_pendingIds.isEmpty()) {
|
||||
msgId = (Long)_pendingIds.remove(0);
|
||||
size = (Integer)_pendingSizes.remove(0);
|
||||
msgId = _pendingIds.remove(0);
|
||||
size = _pendingSizes.remove(0);
|
||||
}
|
||||
}
|
||||
if ( (msgId != null) && (size != null) ) {
|
||||
@@ -695,8 +733,15 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
/** configure the listener */
|
||||
public void setSessionListener(I2PSessionListener lsnr) { _sessionListener = lsnr; }
|
||||
|
||||
/** has the session been closed (or not yet connected)? */
|
||||
public boolean isClosed() { return _closed; }
|
||||
/**
|
||||
* Has the session been closed (or not yet connected)?
|
||||
* False when open and during transitions. Unsynchronized.
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
synchronized (_stateLock) {
|
||||
return _state == State.CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliver an I2CP message to the router
|
||||
@@ -713,7 +758,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
if (!_queue.offer(message, MAX_SEND_WAIT))
|
||||
throw new I2PSessionException("Timed out waiting while write queue was full");
|
||||
} catch (InterruptedException ie) {
|
||||
throw new I2PSessionException("Interrupted while write queue was full", ie);
|
||||
throw new I2PSessionException("Interrupted", ie);
|
||||
}
|
||||
} else if (_writer == null) {
|
||||
throw new I2PSessionException("Already closed");
|
||||
@@ -756,21 +801,16 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
/**
|
||||
* Tear down the session, and do NOT reconnect.
|
||||
*
|
||||
* Blocks if session has not been fully started.
|
||||
* Will interrupt an open in progress.
|
||||
*/
|
||||
public void destroySession(boolean sendDisconnect) {
|
||||
while (_opening) {
|
||||
synchronized (_openingWait) {
|
||||
try {
|
||||
_openingWait.wait(1000);
|
||||
} catch (InterruptedException ie) { // nop
|
||||
}
|
||||
}
|
||||
synchronized(_stateLock) {
|
||||
if (_state == State.CLOSING || _state == State.CLOSED)
|
||||
return;
|
||||
changeState(State.CLOSING);
|
||||
}
|
||||
if (_closed) return;
|
||||
|
||||
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "Destroy the session", new Exception("DestroySession()"));
|
||||
_closing = true; // we use this to prevent a race
|
||||
if (sendDisconnect && _producer != null) { // only null if overridden by I2PSimpleSession
|
||||
try {
|
||||
_producer.disconnect(this);
|
||||
@@ -783,19 +823,27 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
// SimpleSession does not initialize
|
||||
if (_availabilityNotifier != null)
|
||||
_availabilityNotifier.stopNotifying();
|
||||
_closed = true;
|
||||
_closing = false;
|
||||
closeSocket();
|
||||
if (_sessionListener != null) _sessionListener.disconnected(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the socket carefully
|
||||
*
|
||||
* Close the socket carefully.
|
||||
*/
|
||||
private void closeSocket() {
|
||||
synchronized(_stateLock) {
|
||||
changeState(State.CLOSING);
|
||||
locked_closeSocket();
|
||||
changeState(State.CLOSED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the socket carefully.
|
||||
* Caller must change state.
|
||||
*/
|
||||
private void locked_closeSocket() {
|
||||
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "Closing the socket", new Exception("closeSocket"));
|
||||
_closed = true;
|
||||
if (_reader != null) {
|
||||
_reader.stopReading();
|
||||
_reader = null;
|
||||
@@ -830,8 +878,15 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will interrupt a connect in progress.
|
||||
*/
|
||||
protected void disconnect() {
|
||||
if (_closed || _closing) return;
|
||||
synchronized(_stateLock) {
|
||||
if (_state == State.CLOSING || _state == State.CLOSED)
|
||||
return;
|
||||
changeState(State.CLOSING);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Disconnect() called", new Exception("Disconnect"));
|
||||
if (shouldReconnect()) {
|
||||
if (reconnect()) {
|
||||
@@ -842,11 +897,11 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(getPrefix() + "Disconned from the router, and not trying to reconnect further. I hope you're not hoping anything else will happen");
|
||||
_log.error(getPrefix() + "Disconned from the router, and not trying to reconnect");
|
||||
if (_sessionListener != null) _sessionListener.disconnected(this);
|
||||
|
||||
_closed = true;
|
||||
closeSocket();
|
||||
changeState(State.CLOSED);
|
||||
}
|
||||
|
||||
private final static int MAX_RECONNECT_DELAY = 320*1000;
|
||||
@@ -865,7 +920,11 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
i++;
|
||||
if ( (delay > MAX_RECONNECT_DELAY) || (delay <= 0) )
|
||||
delay = MAX_RECONNECT_DELAY;
|
||||
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException ie) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
connect();
|
||||
@@ -970,7 +1029,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
if (rv != null)
|
||||
return rv;
|
||||
}
|
||||
if (_closed)
|
||||
if (isClosed())
|
||||
return null;
|
||||
LookupWaiter waiter = new LookupWaiter(h);
|
||||
_pendingLookups.offer(waiter);
|
||||
@@ -980,7 +1039,9 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
synchronized (waiter) {
|
||||
waiter.wait(maxWait);
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
} catch (InterruptedException ie) {
|
||||
throw new I2PSessionException("Interrupted", ie);
|
||||
}
|
||||
} finally {
|
||||
_pendingLookups.remove(waiter);
|
||||
}
|
||||
@@ -996,14 +1057,16 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
* @return null on failure
|
||||
*/
|
||||
public int[] bandwidthLimits() throws I2PSessionException {
|
||||
if (_closed)
|
||||
if (isClosed())
|
||||
return null;
|
||||
sendMessage(new GetBandwidthLimitsMessage());
|
||||
try {
|
||||
synchronized (_bwReceivedLock) {
|
||||
_bwReceivedLock.wait(5*1000);
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
} catch (InterruptedException ie) {
|
||||
throw new I2PSessionException("Interrupted", ie);
|
||||
}
|
||||
return _bwLimits;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,12 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
/** Don't expect any MSMs from the router for outbound traffic @since 0.8.1 */
|
||||
protected boolean _noEffort;
|
||||
|
||||
/** for extension */
|
||||
protected I2PSessionImpl2(I2PAppContext context, Properties options) {
|
||||
super(context, options);
|
||||
/**
|
||||
* for extension by SimpleSession (no dest)
|
||||
*/
|
||||
protected I2PSessionImpl2(I2PAppContext context, Properties options,
|
||||
I2PClientMessageHandlerMap handlerMap) {
|
||||
super(context, options, handlerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ package net.i2p.client;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Properties;
|
||||
@@ -37,53 +38,57 @@ class I2PSimpleSession extends I2PSessionImpl2 {
|
||||
* @throws I2PSessionException if there is a problem
|
||||
*/
|
||||
public I2PSimpleSession(I2PAppContext context, Properties options) throws I2PSessionException {
|
||||
super(context, options);
|
||||
_handlerMap = new SimpleMessageHandlerMap(context);
|
||||
super(context, options, new SimpleMessageHandlerMap(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the router and establish a session. This call blocks until
|
||||
* a session is granted.
|
||||
*
|
||||
* NOT threadsafe, do not call from multiple threads.
|
||||
*
|
||||
* @throws I2PSessionException if there is a configuration error or the router is
|
||||
* not reachable
|
||||
*/
|
||||
@Override
|
||||
public void connect() throws I2PSessionException {
|
||||
_closed = false;
|
||||
|
||||
changeState(State.OPENING);
|
||||
boolean success = false;
|
||||
try {
|
||||
// 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.parseBoolean(getOptions().getProperty(PROP_ENABLE_SSL)))
|
||||
_socket = I2CPSSLSocketFactory.createSocket(_context, _hostname, _portNum);
|
||||
else
|
||||
_socket = new Socket(_hostname, _portNum);
|
||||
_out = _socket.getOutputStream();
|
||||
_out.write(I2PClient.PROTOCOL_BYTE);
|
||||
_out.flush();
|
||||
_writer = new ClientWriterRunner(_out, this);
|
||||
InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE);
|
||||
_reader = new I2CPMessageReader(in, this);
|
||||
// protect w/ closeSocket()
|
||||
synchronized(_stateLock) {
|
||||
// If we are in the router JVM, connect using the interal queue
|
||||
if (_context.isRouterContext()) {
|
||||
// _socket 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.parseBoolean(getOptions().getProperty(PROP_ENABLE_SSL)))
|
||||
_socket = I2CPSSLSocketFactory.createSocket(_context, _hostname, _portNum);
|
||||
else
|
||||
_socket = new Socket(_hostname, _portNum);
|
||||
OutputStream out = _socket.getOutputStream();
|
||||
out.write(I2PClient.PROTOCOL_BYTE);
|
||||
out.flush();
|
||||
_writer = new ClientWriterRunner(out, this);
|
||||
InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE);
|
||||
_reader = new I2CPMessageReader(in, this);
|
||||
}
|
||||
}
|
||||
// we do not receive payload messages, so we do not need an AvailabilityNotifier
|
||||
// ... or an Idle timer, or a VerifyUsage
|
||||
_reader.startReading();
|
||||
|
||||
success = true;
|
||||
} catch (UnknownHostException uhe) {
|
||||
_closed = true;
|
||||
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, uhe);
|
||||
} catch (IOException ioe) {
|
||||
_closed = true;
|
||||
throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, ioe);
|
||||
} finally {
|
||||
changeState(success ? State.OPEN : State.CLOSED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* No warranty of any kind, either expressed or implied.
|
||||
*/
|
||||
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
|
||||
/**
|
||||
* 48 byte hash
|
||||
*
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public class Hash384 extends SimpleDataStructure {
|
||||
|
||||
public final static int HASH_LENGTH = 48;
|
||||
|
||||
public Hash384() {
|
||||
super();
|
||||
}
|
||||
|
||||
/** @throws IllegalArgumentException if data is not correct length (null is ok) */
|
||||
public Hash384(byte data[]) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return HASH_LENGTH;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* No warranty of any kind, either expressed or implied.
|
||||
*/
|
||||
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
|
||||
/**
|
||||
* 64 byte hash
|
||||
*
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public class Hash512 extends SimpleDataStructure {
|
||||
|
||||
public final static int HASH_LENGTH = 64;
|
||||
|
||||
public Hash512() {
|
||||
super();
|
||||
}
|
||||
|
||||
/** @throws IllegalArgumentException if data is not correct length (null is ok) */
|
||||
public Hash512(byte data[]) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return HASH_LENGTH;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
|
||||
/**
|
||||
* Succesor to the ".sud" format used in TrustedUpdate.
|
||||
* Format specified in http://www.i2p2.de/updates
|
||||
*
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public class SU3File {
|
||||
|
||||
private final I2PAppContext _context;
|
||||
private final Map<SigningPublicKey, String> _trustedKeys;
|
||||
|
||||
private final File _file;
|
||||
private String _version;
|
||||
private int _versionLength;
|
||||
private String _signer;
|
||||
private int _signerLength;
|
||||
private int _contentType;
|
||||
private long _contentLength;
|
||||
private SigningPublicKey _signerPubkey;
|
||||
private boolean _headerVerified;
|
||||
|
||||
private static final byte[] MAGIC = DataHelper.getUTF8("I2Psu3");
|
||||
private static final int FILE_VERSION = 0;
|
||||
private static final int MIN_VERSION_BYTES = 16;
|
||||
private static final int VERSION_OFFSET = Signature.SIGNATURE_BYTES;
|
||||
|
||||
private static final int TYPE_ZIP = 0;
|
||||
|
||||
private static final int CONTENT_ROUTER = 0;
|
||||
private static final int CONTENT_ROUTER_P200 = 1;
|
||||
private static final int CONTENT_PLUGIN = 2;
|
||||
private static final int CONTENT_RESEED = 3;
|
||||
|
||||
private static final int SIG_DSA_160 = SigType.DSA_SHA1.getCode();
|
||||
|
||||
/**
|
||||
* Uses TrustedUpdate's default keys for verification.
|
||||
*/
|
||||
public SU3File(String file) {
|
||||
this(new File(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses TrustedUpdate's default keys for verification.
|
||||
*/
|
||||
public SU3File(File file) {
|
||||
this(file, (new TrustedUpdate()).getKeys());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param trustedKeys map of pubkey to signer name, null ok if not verifying
|
||||
*/
|
||||
public SU3File(File file, Map<SigningPublicKey, String> trustedKeys) {
|
||||
this(I2PAppContext.getGlobalContext(), file, trustedKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param trustedKeys map of pubkey to signer name, null ok if not verifying
|
||||
*/
|
||||
public SU3File(I2PAppContext context, File file, Map<SigningPublicKey, String> trustedKeys) {
|
||||
_context = context;
|
||||
_file = file;
|
||||
_trustedKeys = trustedKeys;
|
||||
}
|
||||
|
||||
public String getVersionString() throws IOException {
|
||||
verifyHeader();
|
||||
return _version;
|
||||
}
|
||||
|
||||
public String getSignerString() throws IOException {
|
||||
verifyHeader();
|
||||
return _signer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws IOE if verify vails.
|
||||
*/
|
||||
public void verifyHeader() throws IOException {
|
||||
if (_headerVerified)
|
||||
return;
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(_file);
|
||||
verifyHeader(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
IOException ioe = new IOException("foo");
|
||||
ioe.initCause(dfe);
|
||||
throw ioe;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws if verify vails.
|
||||
*/
|
||||
private void verifyHeader(InputStream in) throws IOException, DataFormatException {
|
||||
byte[] magic = new byte[MAGIC.length];
|
||||
DataHelper.read(in, magic);
|
||||
if (!DataHelper.eq(magic, MAGIC))
|
||||
throw new IOException("Not an su3 file");
|
||||
skip(in, 1);
|
||||
int foo = in.read();
|
||||
if (foo != FILE_VERSION)
|
||||
throw new IOException("bad file version");
|
||||
skip(in, 1);
|
||||
int sigType = in.read();
|
||||
// TODO, for other known algos we must start over with a new MessageDigest
|
||||
// (rewind 10 bytes)
|
||||
if (sigType != SIG_DSA_160)
|
||||
throw new IOException("bad sig type");
|
||||
_signerLength = (int) DataHelper.readLong(in, 2);
|
||||
if (_signerLength != Signature.SIGNATURE_BYTES)
|
||||
throw new IOException("bad sig length");
|
||||
skip(in, 1);
|
||||
int _versionLength = in.read();
|
||||
if (_versionLength < MIN_VERSION_BYTES)
|
||||
throw new IOException("bad version length");
|
||||
skip(in, 1);
|
||||
int signerLen = in.read();
|
||||
if (signerLen <= 0)
|
||||
throw new IOException("bad signer length");
|
||||
_contentLength = DataHelper.readLong(in, 8);
|
||||
if (_contentLength <= 0)
|
||||
throw new IOException("bad content length");
|
||||
skip(in, 1);
|
||||
foo = in.read();
|
||||
if (foo != TYPE_ZIP)
|
||||
throw new IOException("bad type");
|
||||
skip(in, 1);
|
||||
_contentType = in.read();
|
||||
if (_contentType < CONTENT_ROUTER || _contentType > CONTENT_RESEED)
|
||||
throw new IOException("bad content type");
|
||||
skip(in, 12);
|
||||
|
||||
byte[] data = new byte[_versionLength];
|
||||
int bytesRead = DataHelper.read(in, data);
|
||||
if (bytesRead != _versionLength)
|
||||
throw new EOFException();
|
||||
int zbyte;
|
||||
for (zbyte = 0; zbyte < _versionLength; zbyte++) {
|
||||
if (data[zbyte] == 0x00)
|
||||
break;
|
||||
}
|
||||
_version = new String(data, 0, zbyte, "UTF-8");
|
||||
|
||||
data = new byte[signerLen];
|
||||
bytesRead = DataHelper.read(in, data);
|
||||
if (bytesRead != signerLen)
|
||||
throw new EOFException();
|
||||
_signer = DataHelper.getUTF8(data);
|
||||
if (_trustedKeys != null) {
|
||||
for (Map.Entry<SigningPublicKey, String> e : _trustedKeys.entrySet()) {
|
||||
if (e.getValue().equals(_signer)) {
|
||||
_signerPubkey = e.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_signerPubkey == null)
|
||||
throw new IOException("unknown signer: " + _signer);
|
||||
}
|
||||
_headerVerified = true;
|
||||
}
|
||||
|
||||
/** skip but update digest */
|
||||
private static void skip(InputStream in, int cnt) throws IOException {
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
if (in.read() < 0)
|
||||
throw new EOFException();
|
||||
}
|
||||
}
|
||||
|
||||
private int getContentOffset() throws IOException {
|
||||
verifyHeader();
|
||||
return VERSION_OFFSET + _versionLength + _signerLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* One-pass verify and extract the content.
|
||||
* Recommend extracting to a temp location as the sig is not checked until
|
||||
* after extraction. This will delete the file if the sig does not verify.
|
||||
* Throws IOE on all format errors.
|
||||
*
|
||||
* @param migrateTo the output file, probably in zip format
|
||||
* @return true if signature is good
|
||||
*/
|
||||
public boolean verifyAndMigrate(File migrateTo) throws IOException {
|
||||
DigestInputStream in = null;
|
||||
OutputStream out = null;
|
||||
boolean rv = false;
|
||||
try {
|
||||
MessageDigest md = SHA1.getInstance();
|
||||
in = new DigestInputStream(new BufferedInputStream(new FileInputStream(_file)), md);
|
||||
if (!_headerVerified)
|
||||
verifyHeader(in);
|
||||
else
|
||||
skip(in, getContentOffset());
|
||||
if (_signerPubkey == null)
|
||||
throw new IOException("unknown signer: " + _signer);
|
||||
out = new FileOutputStream(migrateTo);
|
||||
byte[] buf = new byte[16*1024];
|
||||
long tot = 0;
|
||||
while (tot < _contentLength) {
|
||||
int read = in.read(buf, 0, (int) Math.min(buf.length, _contentLength - tot));
|
||||
if (read < 0)
|
||||
throw new EOFException();
|
||||
out.write(buf, 0, read);
|
||||
tot += read;
|
||||
}
|
||||
byte[] sha = md.digest();
|
||||
in.on(false);
|
||||
Signature signature = new Signature();
|
||||
signature.readBytes(in);
|
||||
SHA1Hash hash = new SHA1Hash(sha);
|
||||
rv = _context.dsa().verifySignature(signature, hash, _signerPubkey);
|
||||
} catch (DataFormatException dfe) {
|
||||
IOException ioe = new IOException("foo");
|
||||
ioe.initCause(dfe);
|
||||
throw ioe;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
if (!rv)
|
||||
migrateTo.delete();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* One-pass wrap and sign the content.
|
||||
* Writes to the file specified in the constructor.
|
||||
* Throws on all errors.
|
||||
*
|
||||
* @param content the input file, probably in zip format
|
||||
* @param contentType 0-255, 0 for zip
|
||||
* @param version 1-255 bytes when converted to UTF-8
|
||||
* @param signer ID of the public key, 1-255 bytes when converted to UTF-8
|
||||
*/
|
||||
public void write(File content, int contentType, String version,
|
||||
String signer, SigningPrivateKey privkey) throws IOException {
|
||||
InputStream in = null;
|
||||
DigestOutputStream out = null;
|
||||
boolean ok = false;
|
||||
try {
|
||||
in = new BufferedInputStream(new FileInputStream(content));
|
||||
MessageDigest md = SHA1.getInstance();
|
||||
out = new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(_file)), md);
|
||||
out.write(MAGIC);
|
||||
out.write((byte) 0);
|
||||
out.write((byte) FILE_VERSION);
|
||||
out.write((byte) 0);
|
||||
out.write((byte) SIG_DSA_160);
|
||||
DataHelper.writeLong(out, 2, Signature.SIGNATURE_BYTES);
|
||||
out.write((byte) 0);
|
||||
byte[] verBytes = DataHelper.getUTF8(version);
|
||||
if (verBytes.length == 0 || verBytes.length > 255)
|
||||
throw new IllegalArgumentException("bad version length");
|
||||
int verLen = Math.max(verBytes.length, MIN_VERSION_BYTES);
|
||||
out.write((byte) verLen);
|
||||
out.write((byte) 0);
|
||||
byte[] signerBytes = DataHelper.getUTF8(signer);
|
||||
if (signerBytes.length == 0 || signerBytes.length > 255)
|
||||
throw new IllegalArgumentException("bad signer length");
|
||||
out.write((byte) signerBytes.length);
|
||||
long contentLength = content.length();
|
||||
if (contentLength <= 0)
|
||||
throw new IllegalArgumentException("No content");
|
||||
DataHelper.writeLong(out, 8, contentLength);
|
||||
out.write((byte) 0);
|
||||
out.write((byte) TYPE_ZIP);
|
||||
out.write((byte) 0);
|
||||
if (contentType < 0 || contentType > 255)
|
||||
throw new IllegalArgumentException("bad content type");
|
||||
out.write((byte) contentType);
|
||||
out.write(new byte[12]);
|
||||
out.write(verBytes);
|
||||
if (verBytes.length < MIN_VERSION_BYTES)
|
||||
out.write(new byte[MIN_VERSION_BYTES - verBytes.length]);
|
||||
out.write(signerBytes);
|
||||
|
||||
byte[] buf = new byte[16*1024];
|
||||
long tot = 0;
|
||||
while (tot < contentLength) {
|
||||
int read = in.read(buf, 0, (int) Math.min(buf.length, contentLength - tot));
|
||||
if (read < 0)
|
||||
throw new EOFException();
|
||||
out.write(buf, 0, read);
|
||||
tot += read;
|
||||
}
|
||||
|
||||
byte[] sha = md.digest();
|
||||
out.on(false);
|
||||
SHA1Hash hash = new SHA1Hash(sha);
|
||||
Signature signature = _context.dsa().sign(hash, privkey);
|
||||
signature.writeBytes(out);
|
||||
ok = true;
|
||||
} catch (DataFormatException dfe) {
|
||||
IOException ioe = new IOException("foo");
|
||||
ioe.initCause(dfe);
|
||||
throw ioe;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
if (!ok)
|
||||
_file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses command line arguments when this class is used from the command
|
||||
* line.
|
||||
* Exits 1 on failure so this can be used in scripts.
|
||||
*
|
||||
* @param args Command line parameters.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
boolean ok = false;
|
||||
try {
|
||||
if ("showversion".equals(args[0])) {
|
||||
ok = showVersionCLI(args[1]);
|
||||
} else if ("sign".equals(args[0])) {
|
||||
ok = signCLI(args[1], args[2], args[3], args[4], args[5]);
|
||||
} else if ("verifysig".equals(args[0])) {
|
||||
ok = verifySigCLI(args[1]);
|
||||
} else {
|
||||
showUsageCLI();
|
||||
}
|
||||
} catch (ArrayIndexOutOfBoundsException aioobe) {
|
||||
showUsageCLI();
|
||||
}
|
||||
if (!ok)
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private static final void showUsageCLI() {
|
||||
System.err.println("Usage: SU3File showversion signedFile.su3");
|
||||
System.err.println(" SU3File sign inputFile.zip signedFile.su3 privateKeyFile version signerName@mail.i2p");
|
||||
System.err.println(" SU3File verifysig signedFile.su3");
|
||||
}
|
||||
|
||||
/** @return success */
|
||||
private static final boolean showVersionCLI(String signedFile) {
|
||||
try {
|
||||
SU3File file = new SU3File(new File(signedFile), null);
|
||||
String versionString = file.getVersionString();
|
||||
if (versionString.equals(""))
|
||||
System.out.println("No version string found in file '" + signedFile + "'");
|
||||
else
|
||||
System.out.println("Version: " + versionString);
|
||||
return !versionString.equals("");
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @return success */
|
||||
private static final boolean signCLI(String inputFile, String signedFile, String privateKeyFile,
|
||||
String version, String signerName) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(privateKeyFile);
|
||||
SigningPrivateKey spk = new SigningPrivateKey();
|
||||
spk.readBytes(in);
|
||||
in.close();
|
||||
SU3File file = new SU3File(signedFile);
|
||||
file.write(new File(inputFile), CONTENT_ROUTER, version, signerName, spk);
|
||||
System.out.println("Input file '" + inputFile + "' signed and written to '" + signedFile + "'");
|
||||
return true;
|
||||
} catch (DataFormatException dfe) {
|
||||
System.out.println("Error signing input file '" + inputFile + "'");
|
||||
dfe.printStackTrace();
|
||||
return false;
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error signing input file '" + inputFile + "'");
|
||||
ioe.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return valid */
|
||||
private static final boolean verifySigCLI(String signedFile) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
SU3File file = new SU3File(signedFile);
|
||||
boolean isValidSignature = file.verifyAndMigrate(new File("/dev/null"));
|
||||
if (isValidSignature)
|
||||
System.out.println("Signature VALID (signed by " + file.getSignerString() + ')');
|
||||
else
|
||||
System.out.println("Signature INVALID (signed by " + file.getSignerString() + ')');
|
||||
return isValidSignature;
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error verifying input file '" + signedFile + "'");
|
||||
ioe.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Defines the properties for various signature types
|
||||
* that I2P supports or may someday support.
|
||||
*
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public enum SigType {
|
||||
/**
|
||||
* DSA_SHA1 is the default.
|
||||
* Pubkey 128 bytes; privkey 20 bytes; hash 20 bytes; sig 40 bytes
|
||||
* @since 0.9.8
|
||||
*/
|
||||
DSA_SHA1(0, 128, 20, 20, 40, "SHA-1", "SHA1withDSA"),
|
||||
/** Pubkey 40 bytes; privkey 20 bytes; hash 20 bytes; sig 40 bytes */
|
||||
ECDSA_SHA1(1, 40, 20, 20, 40, "SHA-1", "SHA1withECDSA"),
|
||||
/** Pubkey 64 bytes; privkey 32 bytes; hash 32 bytes; sig 64 bytes */
|
||||
ECDSA_SHA256(2, 64, 32, 32, 64, "SHA-256", "SHA256withECDSA"),
|
||||
/** Pubkey 96 bytes; privkey 48 bytes; hash 48 bytes; sig 96 bytes */
|
||||
ECDSA_SHA384(3, 96, 48, 48, 96, "SHA-384", "SHA384withECDSA"),
|
||||
/** Pubkey 128 bytes; privkey 64 bytes; hash 64 bytes; sig 128 bytes */
|
||||
ECDSA_SHA512(4, 128, 64, 64, 128, "SHA-512", "SHA512withECDSA")
|
||||
|
||||
//MD5
|
||||
//ELGAMAL_SHA256
|
||||
//RSA_SHA1
|
||||
//RSA_SHA256
|
||||
//RSA_SHA384
|
||||
//RSA_SHA512
|
||||
//DSA_2048_224(2, 256, 28, 32, 56, "SHA-256"),
|
||||
// Nonstandard, used by Syndie.
|
||||
// Pubkey 128 bytes; privkey 20 bytes; hash 32 bytes; sig 40 bytes
|
||||
//DSA_1024_160_SHA256(1, 128, 20, 32, 40, "SHA-256", "?"),
|
||||
// Pubkey 256 bytes; privkey 32 bytes; hash 32 bytes; sig 64 bytes
|
||||
//DSA_2048_256(2, 256, 32, 32, 64, "SHA-256", "?"),
|
||||
// Pubkey 384 bytes; privkey 32 bytes; hash 32 bytes; sig 64 bytes
|
||||
//DSA_3072_256(3, 384, 32, 32, 64, "SHA-256", "?"),
|
||||
;
|
||||
|
||||
private final int code, pubkeyLen, privkeyLen, hashLen, sigLen;
|
||||
private final String digestName, algoName;
|
||||
|
||||
SigType(int cod, int pubLen, int privLen, int hLen, int sLen, String mdName, String aName) {
|
||||
code = cod;
|
||||
pubkeyLen = pubLen;
|
||||
privkeyLen = privLen;
|
||||
hashLen = hLen;
|
||||
sigLen = sLen;
|
||||
digestName = mdName;
|
||||
algoName = aName;
|
||||
}
|
||||
|
||||
public int getCode() { return code; }
|
||||
public int getPubkeyLen() { return pubkeyLen; }
|
||||
public int getPrivkeyLen() { return privkeyLen; }
|
||||
public int getHashLen() { return hashLen; }
|
||||
public int getSigLen() { return sigLen; }
|
||||
public String getAlgorithmName() { return algoName; }
|
||||
|
||||
/** @throws UnsupportedOperationException if not supported */
|
||||
public MessageDigest getDigestInstance() {
|
||||
if (digestName.equals("SHA-1"))
|
||||
return SHA1.getInstance();
|
||||
if (digestName.equals("SHA-256"))
|
||||
return SHA256Generator.getDigestInstance();
|
||||
try {
|
||||
return MessageDigest.getInstance(digestName);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<Integer, SigType> BY_CODE = new HashMap<Integer, SigType>();
|
||||
|
||||
static {
|
||||
for (SigType type : SigType.values()) {
|
||||
BY_CODE.put(Integer.valueOf(type.getCode()), type);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return null if not supported */
|
||||
public static SigType getByCode(int code) {
|
||||
return BY_CODE.get(Integer.valueOf(code));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
@@ -209,6 +210,13 @@ riCe6OlAEiNpcc6mMyIYYWFICbrDFTrDR3wXqwc/Jkcx6L5VVWoagpSzbo3yGhc=
|
||||
_log.debug("TrustedUpdate created, trusting " + _trustedKeys.size() + " keys.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
Map<SigningPublicKey, String> getKeys() {
|
||||
return Collections.unmodifiableMap(_trustedKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate keys or names rejected,
|
||||
* except that duplicate empty names are allowed
|
||||
|
||||
@@ -13,6 +13,13 @@ package net.i2p.data;
|
||||
* Defines an end point in the I2P network. The Destination may move around
|
||||
* in the network, but messages sent to the Destination will find it
|
||||
*
|
||||
* Note that the public (encryption) key is essentially unused, since
|
||||
* "end-to-end" encryption was removed in 0.6. The public key in the
|
||||
* LeaseSet is used instead.
|
||||
*
|
||||
* The first bytes of the public key are used for the IV for leaseset encryption,
|
||||
* but that encryption is poorly designed and should be deprecated.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class Destination extends KeysAndCert {
|
||||
|
||||
@@ -31,6 +31,10 @@ import net.i2p.util.RandomSource;
|
||||
* Only the gateways and tunnel IDs in the individual
|
||||
* leases are encrypted.
|
||||
*
|
||||
* WARNING:
|
||||
* Encryption is poorly designed and probably insecure.
|
||||
* Not recommended.
|
||||
*
|
||||
* Encrypted leases are not indicated as such.
|
||||
* The only way to tell a lease is encrypted is to
|
||||
* determine that the listed gateways do not exist.
|
||||
|
||||
@@ -38,7 +38,7 @@ import net.i2p.util.OrderedProperties;
|
||||
* @author jrandom
|
||||
*/
|
||||
public class RouterAddress extends DataStructureImpl {
|
||||
private int _cost;
|
||||
private short _cost;
|
||||
//private Date _expiration;
|
||||
private String _transportStyle;
|
||||
private final Properties _options;
|
||||
@@ -50,16 +50,30 @@ public class RouterAddress extends DataStructureImpl {
|
||||
public static final String PROP_PORT = "port";
|
||||
|
||||
public RouterAddress() {
|
||||
_cost = -1;
|
||||
_options = new OrderedProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* For efficiency when created by a Transport.
|
||||
* @param options not copied; do not reuse or modify
|
||||
* @param cost 0-255
|
||||
* @since IPv6
|
||||
*/
|
||||
public RouterAddress(String style, OrderedProperties options, int cost) {
|
||||
_transportStyle = style;
|
||||
_options = options;
|
||||
if (cost < 0 || cost > 255)
|
||||
throw new IllegalArgumentException();
|
||||
_cost = (short) cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the weighted cost of this address, relative to other methods of
|
||||
* contacting this router. The value 0 means free and 255 means really expensive.
|
||||
* No value above 255 is allowed.
|
||||
*
|
||||
* Unused before 0.7.12
|
||||
* @return 0-255
|
||||
*/
|
||||
public int getCost() {
|
||||
return _cost;
|
||||
@@ -67,12 +81,18 @@ public class RouterAddress extends DataStructureImpl {
|
||||
|
||||
/**
|
||||
* Configure the weighted cost of using the address.
|
||||
* No value above 255 is allowed.
|
||||
* No value negative or above 255 is allowed.
|
||||
*
|
||||
* WARNING - do not change cost on a published address or it will break the RI sig.
|
||||
* There is no check here.
|
||||
* Rarely used, use 3-arg constructor.
|
||||
*
|
||||
* NTCP is set to 10 and SSU to 5 by default, unused before 0.7.12
|
||||
*/
|
||||
public void setCost(int cost) {
|
||||
_cost = cost;
|
||||
if (cost < 0 || cost > 255)
|
||||
throw new IllegalArgumentException();
|
||||
_cost = (short) cost;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,6 +133,7 @@ public class RouterAddress extends DataStructureImpl {
|
||||
* Configure the type of transport that must be used to communicate on this address
|
||||
*
|
||||
* @throws IllegalStateException if was already set
|
||||
* @deprecated unused, use 3-arg constructor
|
||||
*/
|
||||
public void setTransportStyle(String transportStyle) {
|
||||
if (_transportStyle != null)
|
||||
@@ -152,6 +173,7 @@ public class RouterAddress extends DataStructureImpl {
|
||||
* Makes a copy.
|
||||
* @param options non-null
|
||||
* @throws IllegalStateException if was already set
|
||||
* @deprecated unused, use 3-arg constructor
|
||||
*/
|
||||
public void setOptions(Properties options) {
|
||||
if (!_options.isEmpty())
|
||||
@@ -171,7 +193,7 @@ public class RouterAddress extends DataStructureImpl {
|
||||
if (_ip != null)
|
||||
return _ip;
|
||||
byte[] rv = null;
|
||||
String host = _options.getProperty(PROP_HOST);
|
||||
String host = getHost();
|
||||
if (host != null) {
|
||||
rv = Addresses.getIP(host);
|
||||
if (rv != null &&
|
||||
@@ -183,6 +205,17 @@ public class RouterAddress extends DataStructureImpl {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience, same as getOption("host").
|
||||
* Does no parsing, so faster than getIP().
|
||||
*
|
||||
* @return host string or null
|
||||
* @since IPv6
|
||||
*/
|
||||
public String getHost() {
|
||||
return _options.getProperty(PROP_HOST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Caching version of Integer.parseInt(getOption("port"))
|
||||
* Caches valid ports 1-65535 only.
|
||||
@@ -212,7 +245,7 @@ public class RouterAddress extends DataStructureImpl {
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
if (_transportStyle != null)
|
||||
throw new IllegalStateException();
|
||||
_cost = (int) DataHelper.readLong(in, 1);
|
||||
_cost = (short) DataHelper.readLong(in, 1);
|
||||
//_expiration = DataHelper.readDate(in);
|
||||
DataHelper.readDate(in);
|
||||
_transportStyle = DataHelper.readString(in);
|
||||
@@ -229,8 +262,8 @@ public class RouterAddress extends DataStructureImpl {
|
||||
* readin and the signature will fail.
|
||||
*/
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if ((_cost < 0) || (_transportStyle == null))
|
||||
throw new DataFormatException("Not enough data to write a router address");
|
||||
if (_transportStyle == null)
|
||||
throw new DataFormatException("uninitialized");
|
||||
DataHelper.writeLong(out, 1, _cost);
|
||||
//DataHelper.writeDate(out, _expiration);
|
||||
DataHelper.writeDate(out, null);
|
||||
@@ -238,28 +271,44 @@ public class RouterAddress extends DataStructureImpl {
|
||||
DataHelper.writeProperties(out, _options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport, host, and port only.
|
||||
* Never look at cost or other properties.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object == this) return true;
|
||||
if ((object == null) || !(object instanceof RouterAddress)) return false;
|
||||
RouterAddress addr = (RouterAddress) object;
|
||||
// let's keep this fast as we are putting an address into the RouterInfo set frequently
|
||||
return
|
||||
_cost == addr._cost &&
|
||||
getPort() == addr.getPort() &&
|
||||
DataHelper.eq(getHost(), addr.getHost()) &&
|
||||
DataHelper.eq(_transportStyle, addr._transportStyle);
|
||||
//DataHelper.eq(_options, addr._options) &&
|
||||
//DataHelper.eq(_expiration, addr._expiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything, including Transport, host, port, options, and cost
|
||||
* @param addr may be null
|
||||
* @since IPv6
|
||||
*/
|
||||
public boolean deepEquals(RouterAddress addr) {
|
||||
return
|
||||
equals(addr) &&
|
||||
_cost == addr._cost &&
|
||||
_options.equals(addr._options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just use a few items for speed (expiration is always null).
|
||||
* Never look at cost or other properties.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(_transportStyle) ^
|
||||
DataHelper.hashCode(getIP()) ^
|
||||
getPort() ^
|
||||
_cost;
|
||||
getPort();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,10 +320,10 @@ public class RouterAddress extends DataStructureImpl {
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
buf.append("[RouterAddress: ");
|
||||
buf.append("\n\tTransportStyle: ").append(_transportStyle);
|
||||
buf.append("\n\tType: ").append(_transportStyle);
|
||||
buf.append("\n\tCost: ").append(_cost);
|
||||
//buf.append("\n\tExpiration: ").append(_expiration);
|
||||
buf.append("\n\tOptions: #: ").append(_options.size());
|
||||
buf.append("\n\tOptions (").append(_options.size()).append("):");
|
||||
for (Map.Entry e : _options.entrySet()) {
|
||||
String key = (String) e.getKey();
|
||||
String val = (String) e.getValue();
|
||||
|
||||
@@ -61,7 +61,7 @@ public class RouterInfo extends DatabaseEntry {
|
||||
private final Properties _options;
|
||||
private volatile boolean _validated;
|
||||
private volatile boolean _isValid;
|
||||
private volatile String _stringified;
|
||||
//private volatile String _stringified;
|
||||
private volatile byte _byteified[];
|
||||
private volatile int _hashCode;
|
||||
private volatile boolean _hashCodeInitialized;
|
||||
@@ -613,30 +613,34 @@ public class RouterInfo extends DatabaseEntry {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (_stringified != null) return _stringified;
|
||||
StringBuilder buf = new StringBuilder(5*1024);
|
||||
//if (_stringified != null) return _stringified;
|
||||
StringBuilder buf = new StringBuilder(1024);
|
||||
buf.append("[RouterInfo: ");
|
||||
buf.append("\n\tIdentity: ").append(_identity);
|
||||
buf.append("\n\tSignature: ").append(_signature);
|
||||
buf.append("\n\tPublished on: ").append(new Date(_published));
|
||||
buf.append("\n\tAddresses: #: ").append(_addresses.size());
|
||||
for (RouterAddress addr : _addresses) {
|
||||
buf.append("\n\t\tAddress: ").append(addr);
|
||||
buf.append("\n\tPublished: ").append(new Date(_published));
|
||||
if (_peers != null) {
|
||||
buf.append("\n\tPeers (").append(_peers.size()).append("):");
|
||||
for (Hash hash : _peers) {
|
||||
buf.append("\n\t\tPeer hash: ").append(hash);
|
||||
}
|
||||
}
|
||||
Set<Hash> peers = getPeers();
|
||||
buf.append("\n\tPeers: #: ").append(peers.size());
|
||||
for (Hash hash : peers) {
|
||||
buf.append("\n\t\tPeer hash: ").append(hash);
|
||||
}
|
||||
buf.append("\n\tOptions: #: ").append(_options.size());
|
||||
buf.append("\n\tOptions (").append(_options.size()).append("):");
|
||||
for (Map.Entry e : _options.entrySet()) {
|
||||
String key = (String) e.getKey();
|
||||
String val = (String) e.getValue();
|
||||
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
|
||||
}
|
||||
if (!_addresses.isEmpty()) {
|
||||
buf.append("\n\tAddresses (").append(_addresses.size()).append("):");
|
||||
for (RouterAddress addr : _addresses) {
|
||||
buf.append("\n\t").append(addr);
|
||||
}
|
||||
}
|
||||
buf.append("]");
|
||||
_stringified = buf.toString();
|
||||
return _stringified;
|
||||
String rv = buf.toString();
|
||||
//_stringified = rv;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,8 @@ package net.i2p.data;
|
||||
*
|
||||
*/
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
|
||||
/**
|
||||
* Defines the signature as defined by the I2P data structure spec.
|
||||
* A signature is a 40-byte array verifying the authenticity of some data
|
||||
@@ -20,19 +22,47 @@ package net.i2p.data;
|
||||
* @author jrandom
|
||||
*/
|
||||
public class Signature extends SimpleDataStructure {
|
||||
public final static int SIGNATURE_BYTES = 40;
|
||||
private static final SigType DEF_TYPE = SigType.DSA_SHA1;
|
||||
/** 40 */
|
||||
public final static int SIGNATURE_BYTES = DEF_TYPE.getSigLen();
|
||||
/** all zeros */
|
||||
public final static byte[] FAKE_SIGNATURE = new byte[SIGNATURE_BYTES];
|
||||
|
||||
private final SigType _type;
|
||||
|
||||
public Signature() {
|
||||
this(DEF_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public Signature(SigType type) {
|
||||
super();
|
||||
_type = type;
|
||||
}
|
||||
|
||||
public Signature(byte data[]) {
|
||||
super(data);
|
||||
this(DEF_TYPE, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public Signature(SigType type, byte data[]) {
|
||||
super();
|
||||
_type = type;
|
||||
setData(data);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return SIGNATURE_BYTES;
|
||||
return _type.getSigLen();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public SigType getType() {
|
||||
return _type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ package net.i2p.data;
|
||||
*/
|
||||
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.SigType;
|
||||
|
||||
/**
|
||||
* Defines the SigningPrivateKey as defined by the I2P data structure spec.
|
||||
@@ -20,14 +21,34 @@ import net.i2p.crypto.KeyGenerator;
|
||||
* @author jrandom
|
||||
*/
|
||||
public class SigningPrivateKey extends SimpleDataStructure {
|
||||
public final static int KEYSIZE_BYTES = 20;
|
||||
private static final SigType DEF_TYPE = SigType.DSA_SHA1;
|
||||
public final static int KEYSIZE_BYTES = DEF_TYPE.getPrivkeyLen();
|
||||
|
||||
private final SigType _type;
|
||||
|
||||
public SigningPrivateKey() {
|
||||
this(DEF_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public SigningPrivateKey(SigType type) {
|
||||
super();
|
||||
_type = type;
|
||||
}
|
||||
|
||||
public SigningPrivateKey(byte data[]) {
|
||||
super(data);
|
||||
this(DEF_TYPE, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public SigningPrivateKey(SigType type, byte data[]) {
|
||||
super();
|
||||
_type = type;
|
||||
setData(data);
|
||||
}
|
||||
|
||||
/** constructs from base64
|
||||
@@ -35,12 +56,20 @@ public class SigningPrivateKey extends SimpleDataStructure {
|
||||
* on a prior instance of SigningPrivateKey
|
||||
*/
|
||||
public SigningPrivateKey(String base64Data) throws DataFormatException {
|
||||
super();
|
||||
this();
|
||||
fromBase64(base64Data);
|
||||
}
|
||||
|
||||
|
||||
public int length() {
|
||||
return KEYSIZE_BYTES;
|
||||
return _type.getPrivkeyLen();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public SigType getType() {
|
||||
return _type;
|
||||
}
|
||||
|
||||
/** converts this signing private key to its public equivalent
|
||||
|
||||
@@ -12,6 +12,8 @@ package net.i2p.data;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
|
||||
/**
|
||||
* Defines the SigningPublicKey as defined by the I2P data structure spec.
|
||||
* A signing public key is 128 byte Integer. The public key represents only the
|
||||
@@ -21,11 +23,14 @@ import java.io.IOException;
|
||||
* @author jrandom
|
||||
*/
|
||||
public class SigningPublicKey extends SimpleDataStructure {
|
||||
public final static int KEYSIZE_BYTES = 128;
|
||||
private static final SigType DEF_TYPE = SigType.DSA_SHA1;
|
||||
public final static int KEYSIZE_BYTES = DEF_TYPE.getPubkeyLen();
|
||||
private static final int CACHE_SIZE = 1024;
|
||||
|
||||
private static final SDSCache<SigningPublicKey> _cache = new SDSCache(SigningPublicKey.class, KEYSIZE_BYTES, CACHE_SIZE);
|
||||
|
||||
private final SigType _type;
|
||||
|
||||
/**
|
||||
* Pull from cache or return new
|
||||
* @throws AIOOBE if not enough bytes
|
||||
@@ -44,11 +49,28 @@ public class SigningPublicKey extends SimpleDataStructure {
|
||||
}
|
||||
|
||||
public SigningPublicKey() {
|
||||
this(DEF_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public SigningPublicKey(SigType type) {
|
||||
super();
|
||||
_type = type;
|
||||
}
|
||||
|
||||
public SigningPublicKey(byte data[]) {
|
||||
super(data);
|
||||
this(DEF_TYPE, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public SigningPublicKey(SigType type, byte data[]) {
|
||||
super();
|
||||
_type = type;
|
||||
setData(data);
|
||||
}
|
||||
|
||||
/** constructs from base64
|
||||
@@ -56,11 +78,18 @@ public class SigningPublicKey extends SimpleDataStructure {
|
||||
* on a prior instance of SigningPublicKey
|
||||
*/
|
||||
public SigningPublicKey(String base64Data) throws DataFormatException {
|
||||
super();
|
||||
this();
|
||||
fromBase64(base64Data);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return KEYSIZE_BYTES;
|
||||
return _type.getPubkeyLen();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public SigType getType() {
|
||||
return _type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,17 +31,13 @@ import net.i2p.crypto.SHA256Generator;
|
||||
*/
|
||||
public abstract class SimpleDataStructure extends DataStructureImpl {
|
||||
protected byte[] _data;
|
||||
/** this is just to avoid lots of calls to length() */
|
||||
protected final int _length;
|
||||
|
||||
/** A new instance with the data set to null. Call readBytes(), setData(), or fromByteArray() after this to set the data */
|
||||
public SimpleDataStructure() {
|
||||
_length = length();
|
||||
}
|
||||
|
||||
/** @throws IllegalArgumentException if data is not the legal number of bytes (but null is ok) */
|
||||
public SimpleDataStructure(byte data[]) {
|
||||
_length = length();
|
||||
setData(data);
|
||||
}
|
||||
|
||||
@@ -68,8 +64,8 @@ public abstract class SimpleDataStructure extends DataStructureImpl {
|
||||
public void setData(byte[] data) {
|
||||
if (_data != null)
|
||||
throw new RuntimeException("Data already set");
|
||||
if (data != null && data.length != _length)
|
||||
throw new IllegalArgumentException("Bad data length: " + data.length + "; required: " + _length);
|
||||
if (data != null && data.length != length())
|
||||
throw new IllegalArgumentException("Bad data length: " + data.length + "; required: " + length());
|
||||
_data = data;
|
||||
}
|
||||
|
||||
@@ -81,9 +77,10 @@ public abstract class SimpleDataStructure extends DataStructureImpl {
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
if (_data != null)
|
||||
throw new RuntimeException("Data already set");
|
||||
_data = new byte[_length];
|
||||
int length = length();
|
||||
_data = new byte[length];
|
||||
int read = read(in, _data);
|
||||
if (read != _length) throw new DataFormatException("Not enough bytes to read the data");
|
||||
if (read != length) throw new DataFormatException("Not enough bytes to read the data");
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
@@ -109,8 +106,8 @@ public abstract class SimpleDataStructure extends DataStructureImpl {
|
||||
byte[] d = Base64.decode(data);
|
||||
if (d == null)
|
||||
throw new DataFormatException("Bad Base64 encoded data");
|
||||
if (d.length != _length)
|
||||
throw new DataFormatException("Bad decoded data length, expected " + _length + " got " + d.length);
|
||||
if (d.length != length())
|
||||
throw new DataFormatException("Bad decoded data length, expected " + length() + " got " + d.length);
|
||||
// call setData() instead of _data = data in case overridden
|
||||
setData(d);
|
||||
}
|
||||
@@ -141,8 +138,8 @@ public abstract class SimpleDataStructure extends DataStructureImpl {
|
||||
@Override
|
||||
public void fromByteArray(byte data[]) throws DataFormatException {
|
||||
if (data == null) throw new DataFormatException("Null data passed in");
|
||||
if (data.length != _length)
|
||||
throw new DataFormatException("Bad data length: " + data.length + "; required: " + _length);
|
||||
if (data.length != length())
|
||||
throw new DataFormatException("Bad data length: " + data.length + "; required: " + length());
|
||||
// call setData() instead of _data = data in case overridden
|
||||
setData(data);
|
||||
}
|
||||
@@ -151,12 +148,13 @@ public abstract class SimpleDataStructure extends DataStructureImpl {
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
buf.append('[').append(getClass().getSimpleName()).append(": ");
|
||||
int length = length();
|
||||
if (_data == null) {
|
||||
buf.append("null");
|
||||
} else if (_length <= 32) {
|
||||
} else if (length <= 32) {
|
||||
buf.append(toBase64());
|
||||
} else {
|
||||
buf.append("size: ").append(Integer.toString(_length));
|
||||
buf.append("size: ").append(Integer.toString(length));
|
||||
}
|
||||
buf.append(']');
|
||||
return buf.toString();
|
||||
|
||||
@@ -34,7 +34,7 @@ public abstract class Addresses {
|
||||
return !getAddresses(true, false, false).isEmpty();
|
||||
}
|
||||
|
||||
/** @return the first non-local address it finds, or null */
|
||||
/** @return the first non-local address IPv4 address it finds, or null */
|
||||
public static String getAnyAddress() {
|
||||
SortedSet<String> a = getAddresses();
|
||||
if (!a.isEmpty())
|
||||
@@ -95,7 +95,7 @@ public abstract class Addresses {
|
||||
haveIPv6 = true;
|
||||
if (shouldInclude(allMyIps[i], includeSiteLocal,
|
||||
includeLoopbackAndWildcard, includeIPv6))
|
||||
rv.add(allMyIps[i].getHostAddress());
|
||||
rv.add(stripScope(allMyIps[i].getHostAddress()));
|
||||
}
|
||||
}
|
||||
} catch (UnknownHostException e) {}
|
||||
@@ -113,7 +113,7 @@ public abstract class Addresses {
|
||||
haveIPv6 = true;
|
||||
if (shouldInclude(addr, includeSiteLocal,
|
||||
includeLoopbackAndWildcard, includeIPv6))
|
||||
rv.add(addr.getHostAddress());
|
||||
rv.add(stripScope(addr.getHostAddress()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,17 @@ public abstract class Addresses {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the trailing "%nn" from Inet6Address.getHostAddress()
|
||||
* @since IPv6
|
||||
*/
|
||||
private static String stripScope(String ip) {
|
||||
int pct = ip.indexOf("%");
|
||||
if (pct > 0)
|
||||
ip = ip.substring(0, pct);
|
||||
return ip;
|
||||
}
|
||||
|
||||
private static boolean shouldInclude(InetAddress ia, boolean includeSiteLocal,
|
||||
boolean includeLoopbackAndWildcard, boolean includeIPv6) {
|
||||
return
|
||||
@@ -137,7 +148,10 @@ public abstract class Addresses {
|
||||
((!ia.isAnyLocalAddress()) &&
|
||||
(!ia.isLoopbackAddress()))) &&
|
||||
(includeSiteLocal ||
|
||||
!ia.isSiteLocalAddress()) &&
|
||||
((!ia.isSiteLocalAddress()) &&
|
||||
// disallow fc00::/8 and fd00::/8 (Unique local addresses RFC 4193)
|
||||
// not recognized as local by InetAddress
|
||||
(ia.getAddress().length != 16 || (ia.getAddress()[0] & 0xfe) != 0xfc))) &&
|
||||
// Hamachi 5/8 allocated to RIPE (30 November 2010)
|
||||
// Removed from TransportImpl.isPubliclyRoutable()
|
||||
// Check moved to here, for now, but will eventually need to
|
||||
|
||||
@@ -262,27 +262,23 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste
|
||||
}
|
||||
}
|
||||
|
||||
/*****
|
||||
/**
|
||||
* Outputs to stdout for dieharder:
|
||||
* <code>
|
||||
* java -cp build/i2p.jar net.i2p.util.FortunaRandomSource | dieharder -a -g 200
|
||||
* </code>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
RandomSource rand = I2PAppContext.getGlobalContext().random();
|
||||
if (true) {
|
||||
for (int i = 0; i < 1000; i++)
|
||||
if (rand.nextFloat() < 0)
|
||||
throw new RuntimeException("negative!");
|
||||
System.out.println("All positive");
|
||||
return;
|
||||
java.util.Properties props = new java.util.Properties();
|
||||
props.setProperty("prng.buffers", "12");
|
||||
I2PAppContext ctx = new I2PAppContext(props);
|
||||
RandomSource rand = ctx.random();
|
||||
byte[] buf = new byte[65536];
|
||||
while (true) {
|
||||
rand.nextBytes(buf);
|
||||
System.out.write(buf);
|
||||
}
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
java.util.zip.GZIPOutputStream gos = new java.util.zip.GZIPOutputStream(baos);
|
||||
for (int i = 0; i < 1024*1024; i++) {
|
||||
int c = rand.nextInt(256);
|
||||
gos.write((byte)c);
|
||||
}
|
||||
gos.finish();
|
||||
byte compressed[] = baos.toByteArray();
|
||||
System.out.println("Compressed size of 1MB: " + compressed.length);
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
*****/
|
||||
}
|
||||
|
||||
@@ -92,8 +92,12 @@ public class I2PThread extends Thread {
|
||||
t.printStackTrace();
|
||||
}
|
||||
****/
|
||||
if (t instanceof OutOfMemoryError)
|
||||
if (t instanceof OutOfMemoryError) {
|
||||
fireOOM((OutOfMemoryError)t);
|
||||
} else {
|
||||
System.out.println ("Thread terminated unexpectedly: " + getName());
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
// This creates a new I2PAppContext after it was deleted
|
||||
// in Router.finalShutdown() via RouterContext.killGlobalContext()
|
||||
|
||||
@@ -176,8 +176,7 @@ public class RandomSource extends SecureRandom implements EntropyHarvester {
|
||||
SecureRandom.getInstance("SHA1PRNG").nextBytes(buf);
|
||||
ok = true;
|
||||
} catch (NoSuchAlgorithmException e) {}
|
||||
// why urandom? because /dev/random blocks, and there are arguments
|
||||
// suggesting such blockages are largely meaningless
|
||||
// why urandom? because /dev/random blocks
|
||||
ok = seedFromFile(new File("/dev/urandom"), buf) || ok;
|
||||
// we merge (XOR) in the data from /dev/urandom with our own seedfile
|
||||
File localFile = new File(_context.getConfigDir(), SEEDFILE);
|
||||
@@ -186,6 +185,8 @@ public class RandomSource extends SecureRandom implements EntropyHarvester {
|
||||
}
|
||||
|
||||
/**
|
||||
* XORs the seed into buf
|
||||
*
|
||||
* @param f absolute path
|
||||
* @return success
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.i2p.util;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
@@ -64,43 +65,33 @@ public abstract class Translate {
|
||||
* Use autoboxing to call with ints, longs, floats, etc.
|
||||
*/
|
||||
public static String getString(String s, Object o, I2PAppContext ctx, String bun) {
|
||||
String lang = getLanguage(ctx);
|
||||
if (lang.equals(TEST_LANG))
|
||||
return TEST_STRING + '(' + o + ')' + TEST_STRING;
|
||||
String x = getString(s, ctx, bun);
|
||||
Object[] oArray = new Object[1];
|
||||
oArray[0] = o;
|
||||
try {
|
||||
MessageFormat fmt = new MessageFormat(x, new Locale(lang));
|
||||
return fmt.format(oArray, new StringBuffer(), null).toString();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
System.err.println("Bad format: orig: \"" + s +
|
||||
"\" trans: \"" + x +
|
||||
"\" param: \"" + o +
|
||||
"\" lang: " + lang);
|
||||
return "FIXME: " + x + ' ' + o;
|
||||
}
|
||||
return getString(s, ctx, bun, o);
|
||||
}
|
||||
|
||||
/** for {0} and {1} */
|
||||
public static String getString(String s, Object o, Object o2, I2PAppContext ctx, String bun) {
|
||||
return getString(s, ctx, bun, o, o2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Varargs
|
||||
* @param oArray parameters
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public static String getString(String s, I2PAppContext ctx, String bun, Object... oArray) {
|
||||
String lang = getLanguage(ctx);
|
||||
if (lang.equals(TEST_LANG))
|
||||
return TEST_STRING + '(' + o + ',' + o2 + ')' + TEST_STRING;
|
||||
return TEST_STRING + Arrays.toString(oArray) + TEST_STRING;
|
||||
String x = getString(s, ctx, bun);
|
||||
Object[] oArray = new Object[2];
|
||||
oArray[0] = o;
|
||||
oArray[1] = o2;
|
||||
try {
|
||||
MessageFormat fmt = new MessageFormat(x, new Locale(lang));
|
||||
return fmt.format(oArray, new StringBuffer(), null).toString();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
System.err.println("Bad format: orig: \"" + s +
|
||||
"\" trans: \"" + x +
|
||||
"\" param1: \"" + o +
|
||||
"\" param2: \"" + o2 +
|
||||
"\" lang: " + lang);
|
||||
return "FIXME: " + x + ' ' + o + ',' + o2;
|
||||
"\" params: " + Arrays.toString(oArray) +
|
||||
" lang: " + lang);
|
||||
return "FIXME: " + x + ' ' + Arrays.toString(oArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
package net.i2p.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilterReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Translate.
|
||||
*
|
||||
* Strings are tagged with _("translateme")
|
||||
* or _("translate {0} me", "foo")
|
||||
*
|
||||
* Max two parameters.
|
||||
* String and parameters must be double-quoted (no ngettext, no tagged parameters).
|
||||
* Escape quotes inside quote with \".
|
||||
* Commas and spaces between args are optional.
|
||||
* Entire tag (from '_' to ')') must be on one line.
|
||||
* Multiple tags allowed on one line.
|
||||
*
|
||||
* Also will extract strings to a dummy java file for postprocessing by xgettext - see main().
|
||||
*
|
||||
* @since 0.9.8
|
||||
*/
|
||||
public class TranslateReader extends FilterReader {
|
||||
|
||||
/** all states may transition to START */
|
||||
private enum S {
|
||||
START,
|
||||
/** next state LPAREN */
|
||||
UNDER,
|
||||
/** next state QUOTE */
|
||||
LPAREN,
|
||||
/** next state LPAREN or BACK */
|
||||
QUOTE,
|
||||
/** next state QUOTE */
|
||||
BACK
|
||||
}
|
||||
|
||||
private final String _bundle;
|
||||
private final I2PAppContext _ctx;
|
||||
/** parse in progress */
|
||||
private final StringBuilder _inBuf;
|
||||
/** parsed and translated */
|
||||
private final StringBuilder _outBuf;
|
||||
/** pending string or parameter for translation */
|
||||
private final StringBuilder _argBuf;
|
||||
/** parsed string and parameters */
|
||||
private final List<String> _args;
|
||||
private S _state = S.START;
|
||||
private TagHook _hook;
|
||||
|
||||
private static final int MAX_ARGS = 9;
|
||||
|
||||
/**
|
||||
* @param bundle may be null for tagging only
|
||||
* @param in UTF-8
|
||||
*/
|
||||
public TranslateReader(I2PAppContext ctx, String bundle, InputStream in) throws IOException {
|
||||
super(new BufferedReader(new InputStreamReader(in, "UTF-8")));
|
||||
_ctx = ctx;
|
||||
_bundle = bundle;
|
||||
_args = new ArrayList(4);
|
||||
_inBuf = new StringBuilder(64);
|
||||
_outBuf = new StringBuilder(64);
|
||||
_argBuf = new StringBuilder(64);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int rv = popit();
|
||||
if (rv > 0)
|
||||
return rv;
|
||||
return parse();
|
||||
}
|
||||
|
||||
private int parse() throws IOException {
|
||||
while (true) {
|
||||
int c = in.read();
|
||||
if (c >= 0)
|
||||
pushit((char) c);
|
||||
//System.err.println("State: " + _state + " char: '" + ((char)c) + "'");
|
||||
|
||||
switch (c) {
|
||||
case -1:
|
||||
case '\r':
|
||||
case '\n':
|
||||
return flushit();
|
||||
|
||||
case '_':
|
||||
switch (_state) {
|
||||
case START:
|
||||
_state = S.UNDER;
|
||||
break;
|
||||
case BACK:
|
||||
_state = S.QUOTE;
|
||||
// fall thru
|
||||
case QUOTE:
|
||||
_argBuf.append((char) c);
|
||||
break;
|
||||
default:
|
||||
return flushit();
|
||||
}
|
||||
break;
|
||||
|
||||
case '(':
|
||||
switch (_state) {
|
||||
case UNDER:
|
||||
_args.clear();
|
||||
_state = S.LPAREN;
|
||||
break;
|
||||
case BACK:
|
||||
_state = S.QUOTE;
|
||||
// fall thru
|
||||
case QUOTE:
|
||||
_argBuf.append((char) c);
|
||||
break;
|
||||
default:
|
||||
return flushit();
|
||||
}
|
||||
break;
|
||||
|
||||
case '"':
|
||||
switch (_state) {
|
||||
case LPAREN:
|
||||
// got an opening quote for a parameter
|
||||
if (_args.size() >= MAX_ARGS)
|
||||
return flushit();
|
||||
_argBuf.setLength(0);
|
||||
_state = S.QUOTE;
|
||||
break;
|
||||
case BACK:
|
||||
_argBuf.append((char) c);
|
||||
_state = S.QUOTE;
|
||||
break;
|
||||
case QUOTE:
|
||||
// got a closing quote for a parameter
|
||||
_args.add(_argBuf.toString());
|
||||
_state = S.LPAREN;
|
||||
break;
|
||||
default:
|
||||
return flushit();
|
||||
}
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
switch (_state) {
|
||||
case QUOTE:
|
||||
_state = S.BACK;
|
||||
break;
|
||||
case BACK:
|
||||
_argBuf.append((char) c);
|
||||
_state = S.QUOTE;
|
||||
break;
|
||||
default:
|
||||
return flushit();
|
||||
}
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
case '\t':
|
||||
case ',':
|
||||
switch (_state) {
|
||||
case BACK:
|
||||
_state = S.QUOTE;
|
||||
// fall thru
|
||||
case QUOTE:
|
||||
_argBuf.append((char) c);
|
||||
break;
|
||||
case LPAREN:
|
||||
// ignore whitespace and commas between args
|
||||
break;
|
||||
default:
|
||||
return flushit();
|
||||
}
|
||||
break;
|
||||
|
||||
case ')':
|
||||
switch (_state) {
|
||||
case BACK:
|
||||
_state = S.QUOTE;
|
||||
// fall thru
|
||||
case QUOTE:
|
||||
_argBuf.append((char) c);
|
||||
break;
|
||||
case LPAREN:
|
||||
// Finally, we have something to translate!
|
||||
translate();
|
||||
return popit();
|
||||
default:
|
||||
return flushit();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
switch (_state) {
|
||||
case BACK:
|
||||
_state = S.QUOTE;
|
||||
// fall thru
|
||||
case QUOTE:
|
||||
_argBuf.append((char) c);
|
||||
break;
|
||||
default:
|
||||
return flushit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(char cbuf[], int off, int len) throws IOException {
|
||||
for (int i = 0; i < len; i++) {
|
||||
int c = read();
|
||||
if (c < 0) {
|
||||
if (i == 0)
|
||||
return -1;
|
||||
return i;
|
||||
}
|
||||
cbuf[off + i] = (char) c;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
for (long i = 0; i < n; i++) {
|
||||
int c = read();
|
||||
if (c < 0) {
|
||||
if (i == 0)
|
||||
return -1;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ready() throws IOException {
|
||||
return _outBuf.length() > 0 || _inBuf.length() > 0 ||in.ready();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
_inBuf.setLength(0);
|
||||
_outBuf.setLength(0);
|
||||
_state = S.START;
|
||||
in.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(int readLimit) {}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* put in the pending parse buf
|
||||
*/
|
||||
private void pushit(char c) {
|
||||
_inBuf.append(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* flush _inBuf to _outBuf,
|
||||
* reset state,
|
||||
* and return next char or -1
|
||||
*/
|
||||
private int flushit() {
|
||||
_state = S.START;
|
||||
if (_inBuf.length() > 0) {
|
||||
_outBuf.append(_inBuf);
|
||||
_inBuf.setLength(0);
|
||||
}
|
||||
return popit();
|
||||
}
|
||||
|
||||
/**
|
||||
* return next char from _outBuf or -1
|
||||
*/
|
||||
private int popit() {
|
||||
if (_outBuf.length() > 0) {
|
||||
int rv = _outBuf.charAt(0) & 0xffff;
|
||||
_outBuf.deleteCharAt(0);
|
||||
return rv;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear _inBuf, translate _args to _outBuf,
|
||||
* reset state
|
||||
*/
|
||||
private void translate() {
|
||||
//System.err.println("Translating: " + _args.toString());
|
||||
int argCount = _args.size();
|
||||
if (argCount <= 0 || argCount > MAX_ARGS) {
|
||||
flushit();
|
||||
return;
|
||||
}
|
||||
_state = S.START;
|
||||
_inBuf.setLength(0);
|
||||
if (_hook != null) {
|
||||
_hook.tag(_args);
|
||||
return;
|
||||
}
|
||||
String tx = null;
|
||||
if (argCount == 1)
|
||||
tx = Translate.getString(_args.get(0), _ctx, _bundle);
|
||||
else
|
||||
tx = Translate.getString(_args.get(0), _ctx, _bundle, _args.subList(1, _args.size()).toArray());
|
||||
_outBuf.append(tx);
|
||||
}
|
||||
|
||||
private interface TagHook extends Closeable {
|
||||
public void tag(List<String> args);
|
||||
}
|
||||
|
||||
private static class Tagger implements TagHook {
|
||||
private final PrintStream _out;
|
||||
private final String _name;
|
||||
private int _count;
|
||||
|
||||
public Tagger(String file) throws IOException {
|
||||
_name = file;
|
||||
_out = new PrintStream(file, "UTF-8");
|
||||
_out.println("// Automatically generated, do not edit");
|
||||
_out.println("package dummy;");
|
||||
_out.println("class Dummy {");
|
||||
_out.println(" void dummy() {");
|
||||
}
|
||||
|
||||
public void tag(List<String> args) {
|
||||
if (args.size() <= 0)
|
||||
return;
|
||||
_out.print("\t_(");
|
||||
for (int i = 0; i < args.size(); i++) {
|
||||
if (i > 0)
|
||||
_out.print(", ");
|
||||
_out.print('"');
|
||||
_out.print(args.get(i).replace("\"", "\\\""));
|
||||
_out.print('"');
|
||||
}
|
||||
_out.println(");");
|
||||
_count++;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
_out.println(" }");
|
||||
_out.println("}");
|
||||
if (_out.checkError())
|
||||
throw new IOException();
|
||||
_out.close();
|
||||
System.out.println(_count + " strings written to " + _name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
if (args.length >= 2 && args[0].equals("test"))
|
||||
test(args[1]);
|
||||
else if (args.length >= 2 && args[0].equals("tag"))
|
||||
tag(args);
|
||||
else
|
||||
System.err.println("Usage:\n" +
|
||||
"\ttest file (output to stdout)\n" +
|
||||
"\ttag file (output to file.java)\n" +
|
||||
"\ttag dir outfile\n" +
|
||||
"\ttag file1 [file2...] outfile");
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private static void test(String file) throws IOException {
|
||||
TranslateReader r = new TranslateReader(I2PAppContext.getGlobalContext(),
|
||||
"net.i2p.router.web.messages",
|
||||
new FileInputStream(file));
|
||||
int c;
|
||||
while ((c = r.read()) >= 0) {
|
||||
System.out.print((char)c);
|
||||
}
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
/** @param files ignore 0 */
|
||||
private static void tag(String[] files) throws IOException {
|
||||
char[] buf = new char[256];
|
||||
String outfile;
|
||||
List<String> filelist;
|
||||
if (files.length == 2) {
|
||||
outfile = files[1] + ".java";
|
||||
filelist = Collections.singletonList(files[1]);
|
||||
} else if (files.length == 3 && (new File(files[1])).isDirectory()) {
|
||||
outfile = files[2];
|
||||
File dir = new File(files[1]);
|
||||
File[] listing = dir.listFiles();
|
||||
if (listing == null)
|
||||
throw new IOException();
|
||||
filelist = new ArrayList(listing.length);
|
||||
for (int i = 0; i < listing.length; i++) {
|
||||
File f = listing[i];
|
||||
if (!f.isDirectory())
|
||||
filelist.add(f.getAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
outfile = files[files.length - 1];
|
||||
filelist = Arrays.asList(files).subList(1, files.length - 1);
|
||||
}
|
||||
TagHook tagger = null;
|
||||
try {
|
||||
tagger = new Tagger(outfile);
|
||||
for (String file : filelist) {
|
||||
TranslateReader r = null;
|
||||
try {
|
||||
r = new TranslateReader(I2PAppContext.getGlobalContext(),
|
||||
null,
|
||||
new FileInputStream(file));
|
||||
r._hook = tagger;
|
||||
while (r.read(buf, 0, buf.length) >= 0) {
|
||||
// throw away output
|
||||
}
|
||||
} finally {
|
||||
if (r != null) r.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (tagger != null) tagger.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user