forked from I2P_Developers/i2p.i2p
I2CP:
- Add support for hostname lookups over I2CP with new HostLookup and HostReply messages. - Move username / password from CreateSession to GetDate for early authentication; this is an incompatible chage. Outside router context with authentication enabled, new clients will not work with old routers. Early authentication is not yet enforced, enable with i2cp.strictAuth=true. Will change default to true in a later release. - Block all actions before authentication. - Better disconnect messages to clients for diagnostics - Improve lookup command, add auth command in i2ptunnel CLI for testing - Don't start ClientWriterRunner thread in constructor - Don't flush in ClientWriterRunner unless necessary - Send GetDate even in SimpleSession outside of RouterContext - Improve SetDate wait logic to reduce locks and break out when Disconnect received - Add Disconnect handler to SimpleSession - I2Ping cleanups - Javadocs
This commit is contained in:
@ -8,10 +8,12 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
import net.i2p.internal.PoisonI2CPMessage;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Copied from net.i2p.router.client
|
||||
@ -25,15 +27,24 @@ class ClientWriterRunner implements Runnable {
|
||||
private final I2PSessionImpl _session;
|
||||
private final BlockingQueue<I2CPMessage> _messagesToWrite;
|
||||
private static final AtomicLong __Id = new AtomicLong();
|
||||
//private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ClientWriterRunner.class);
|
||||
|
||||
private static final int MAX_QUEUE_SIZE = 32;
|
||||
private static final long MAX_SEND_WAIT = 10*1000;
|
||||
|
||||
/** starts the thread too */
|
||||
/**
|
||||
* As of 0.9.10 does not start the thread, caller must call startWriting()
|
||||
*/
|
||||
public ClientWriterRunner(OutputStream out, I2PSessionImpl session) {
|
||||
_out = new BufferedOutputStream(out);
|
||||
_session = session;
|
||||
_messagesToWrite = new LinkedBlockingQueue<I2CPMessage>(MAX_QUEUE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.10
|
||||
*/
|
||||
public void startWriting() {
|
||||
Thread t = new I2PAppThread(this, "I2CP Client Writer " + __Id.incrementAndGet(), true);
|
||||
t.start();
|
||||
}
|
||||
@ -76,7 +87,8 @@ class ClientWriterRunner implements Runnable {
|
||||
// only thread, we don't need synchronized
|
||||
try {
|
||||
msg.writeMessage(_out);
|
||||
_out.flush();
|
||||
if (_messagesToWrite.isEmpty())
|
||||
_out.flush();
|
||||
} catch (I2CPMessageException ime) {
|
||||
_session.propogateError("Error writing out the message", ime);
|
||||
_session.disconnect();
|
||||
|
38
core/java/src/net/i2p/client/HostReplyMessageHandler.java
Normal file
38
core/java/src/net/i2p/client/HostReplyMessageHandler.java
Normal file
@ -0,0 +1,38 @@
|
||||
package net.i2p.client;
|
||||
|
||||
/*
|
||||
* Released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
*/
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.HostReplyMessage;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
* Handle I2CP dest replies from the router
|
||||
*
|
||||
* @since 0.9.10
|
||||
*/
|
||||
class HostReplyMessageHandler extends HandlerImpl {
|
||||
|
||||
public HostReplyMessageHandler(I2PAppContext ctx) {
|
||||
super(ctx, HostReplyMessage.MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Handle message " + message);
|
||||
HostReplyMessage msg = (HostReplyMessage) message;
|
||||
Destination d = msg.getDestination();
|
||||
long id = msg.getReqID();
|
||||
if (d != null) {
|
||||
session.destReceived(id, d);
|
||||
} else {
|
||||
session.destLookupFailed(id);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.i2cp.BandwidthLimitsMessage;
|
||||
import net.i2p.data.i2cp.DestReplyMessage;
|
||||
import net.i2p.data.i2cp.DisconnectMessage;
|
||||
import net.i2p.data.i2cp.HostReplyMessage;
|
||||
import net.i2p.data.i2cp.MessagePayloadMessage;
|
||||
import net.i2p.data.i2cp.MessageStatusMessage;
|
||||
import net.i2p.data.i2cp.RequestLeaseSetMessage;
|
||||
@ -40,6 +41,7 @@ class I2PClientMessageHandlerMap {
|
||||
highest = Math.max(highest, MessageStatusMessage.MESSAGE_TYPE);
|
||||
highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE);
|
||||
highest = Math.max(highest, DestReplyMessage.MESSAGE_TYPE);
|
||||
highest = Math.max(highest, HostReplyMessage.MESSAGE_TYPE);
|
||||
highest = Math.max(highest, BandwidthLimitsMessage.MESSAGE_TYPE);
|
||||
highest = Math.max(highest, RequestVariableLeaseSetMessage.MESSAGE_TYPE);
|
||||
|
||||
@ -53,6 +55,7 @@ class I2PClientMessageHandlerMap {
|
||||
_handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context);
|
||||
_handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context);
|
||||
_handlers[RequestVariableLeaseSetMessage.MESSAGE_TYPE] = new RequestVariableLeaseSetMessageHandler(context);
|
||||
_handlers[HostReplyMessage.MESSAGE_TYPE] = new HostReplyMessageHandler(context);
|
||||
}
|
||||
|
||||
public I2CPMessageHandler getHandler(int messageTypeId) {
|
||||
|
@ -214,6 +214,7 @@ public interface I2PSession {
|
||||
public Destination lookupDest(Hash h) throws I2PSessionException;
|
||||
|
||||
/**
|
||||
* Lookup a Destination by Hash.
|
||||
* Blocking.
|
||||
* @param maxWait ms
|
||||
* @since 0.8.3
|
||||
@ -221,6 +222,68 @@ public interface I2PSession {
|
||||
*/
|
||||
public Destination lookupDest(Hash h, long maxWait) throws I2PSessionException;
|
||||
|
||||
/**
|
||||
* Ask the router to lookup a Destination by host name.
|
||||
* Blocking. Waits a max of 10 seconds by default.
|
||||
*
|
||||
* This only makes sense for a b32 hostname, OR outside router context.
|
||||
* Inside router context, just query the naming service.
|
||||
* Outside router context, this does NOT query the context naming service.
|
||||
* Do that first if you expect a local addressbook.
|
||||
*
|
||||
* This will log a warning for non-b32 in router context.
|
||||
*
|
||||
* Suggested implementation:
|
||||
*
|
||||
*<pre>
|
||||
* if (name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p")) {
|
||||
* if (session != null)
|
||||
* return session.lookup(Hash.create(Base32.decode(name.toLowerCase(Locale.US).substring(0, 52))));
|
||||
* else
|
||||
* return ctx.namingService().lookup(name); // simple session for xxx.b32.i2p handled by naming service (optional if you need lookup w/o an existing session)
|
||||
* } else if (ctx.isRouterContext()) {
|
||||
* return ctx.namingService().lookup(name); // hostname from router's naming service
|
||||
* } else {
|
||||
* Destination d = ctx.namingService().lookup(name); // local naming svc, optional
|
||||
* if (d != null)
|
||||
* return d;
|
||||
* if (session != null)
|
||||
* return session.lookup(name);
|
||||
* // simple session (optional if you need lookup w/o an existing session)
|
||||
* Destination rv = null;
|
||||
* I2PClient client = new I2PSimpleClient();
|
||||
* Properties opts = new Properties();
|
||||
* opts.put(I2PClient.PROP_TCP_HOST, host);
|
||||
* opts.put(I2PClient.PROP_TCP_PORT, port);
|
||||
* I2PSession session = null;
|
||||
* try {
|
||||
* session = client.createSession(null, opts);
|
||||
* session.connect();
|
||||
* rv = session.lookupDest(name);
|
||||
* } finally {
|
||||
* if (session != null)
|
||||
* session.destroySession();
|
||||
* }
|
||||
* return rv;
|
||||
* }
|
||||
*</pre>
|
||||
*
|
||||
* Requires router side to be 0.9.10 or higher. If the router is older,
|
||||
* this will return null immediately.
|
||||
*
|
||||
* @since 0.9.10
|
||||
*/
|
||||
public Destination lookupDest(String name) throws I2PSessionException;
|
||||
|
||||
/**
|
||||
* Ask the router to lookup a Destination by host name.
|
||||
* Blocking. See above for details.
|
||||
* @param maxWait ms
|
||||
* @since 0.9.10
|
||||
* @return null on failure
|
||||
*/
|
||||
public Destination lookupDest(String name, long maxWait) throws I2PSessionException;
|
||||
|
||||
/**
|
||||
* Pass updated options to the router.
|
||||
* Does not remove properties previously present but missing from this options parameter.
|
||||
|
@ -19,13 +19,16 @@ import java.net.UnknownHostException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
@ -35,6 +38,7 @@ import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.i2cp.DestLookupMessage;
|
||||
import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
|
||||
import net.i2p.data.i2cp.GetDateMessage;
|
||||
import net.i2p.data.i2cp.HostLookupMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageReader;
|
||||
import net.i2p.data.i2cp.MessagePayloadMessage;
|
||||
@ -46,6 +50,7 @@ import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.I2PSSLSocketFactory;
|
||||
import net.i2p.util.LHMCache;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
@ -98,12 +103,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
|
||||
/** hashes of lookups we are waiting for */
|
||||
protected final LinkedBlockingQueue<LookupWaiter> _pendingLookups = new LinkedBlockingQueue<LookupWaiter>();
|
||||
private final AtomicInteger _lookupID = new AtomicInteger();
|
||||
protected final Object _bwReceivedLock = new Object();
|
||||
protected volatile int[] _bwLimits;
|
||||
|
||||
protected final I2PClientMessageHandlerMap _handlerMap;
|
||||
|
||||
/** used to seperate things out so we can get rid of singletons */
|
||||
/** used to separate things out so we can get rid of singletons */
|
||||
protected final I2PAppContext _context;
|
||||
|
||||
/** monitor for waiting until a lease set has been granted */
|
||||
@ -114,6 +120,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
*/
|
||||
protected enum State {
|
||||
OPENING,
|
||||
/** @since 0.9.10 */
|
||||
GOTDATE,
|
||||
OPEN,
|
||||
CLOSING,
|
||||
CLOSED
|
||||
@ -122,11 +130,6 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
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();
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -139,14 +142,20 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
private boolean _isReduced;
|
||||
private final boolean _fastReceive;
|
||||
private volatile boolean _routerSupportsFastReceive;
|
||||
private volatile boolean _routerSupportsHostLookup;
|
||||
|
||||
/**
|
||||
* Since 0.9.10, key is either a Hash or a String
|
||||
* @since 0.8.9
|
||||
*/
|
||||
private static final Map<Hash, Destination> _lookupCache = new LHMCache<Hash, Destination>(16);
|
||||
private static final Map<Object, Destination> _lookupCache = new LHMCache<Object, Destination>(16);
|
||||
private static final String MIN_HOST_LOOKUP_VERSION = "0.9.10";
|
||||
private static final boolean TEST_LOOKUP = false;
|
||||
|
||||
/** SSL interface (only) @since 0.8.3 */
|
||||
protected static final String PROP_ENABLE_SSL = "i2cp.SSL";
|
||||
protected static final String PROP_USER = "i2cp.username";
|
||||
protected static final String PROP_PW = "i2cp.password";
|
||||
|
||||
private static final long VERIFY_USAGE_TIME = 60*1000;
|
||||
|
||||
@ -159,9 +168,15 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
_routerSupportsFastReceive = _context.isRouterContext() ||
|
||||
(routerVersion != null && routerVersion.length() > 0 &&
|
||||
VersionComparator.comp(routerVersion, MIN_FAST_VERSION) >= 0);
|
||||
_dateReceived = true;
|
||||
synchronized (_dateReceivedLock) {
|
||||
_dateReceivedLock.notifyAll();
|
||||
_routerSupportsHostLookup = _context.isRouterContext() ||
|
||||
TEST_LOOKUP ||
|
||||
(routerVersion != null && routerVersion.length() > 0 &&
|
||||
VersionComparator.comp(routerVersion, MIN_HOST_LOOKUP_VERSION) >= 0);
|
||||
synchronized (_stateLock) {
|
||||
if (_state == State.OPENING) {
|
||||
_state = State.GOTDATE;
|
||||
_stateLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,6 +220,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
_privateKey = null;
|
||||
_signingPrivateKey = null;
|
||||
}
|
||||
_routerSupportsFastReceive = _context.isRouterContext();
|
||||
_routerSupportsHostLookup = _context.isRouterContext();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -239,12 +256,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
// auto-add auth if required, not set in the options, and we are not in the same JVM
|
||||
if ((!_context.isRouterContext()) &&
|
||||
_context.getBooleanProperty("i2cp.auth") &&
|
||||
((!opts.containsKey("i2cp.username")) || (!opts.containsKey("i2cp.password")))) {
|
||||
String configUser = _context.getProperty("i2cp.username");
|
||||
String configPW = _context.getProperty("i2cp.password");
|
||||
((!opts.containsKey(PROP_USER)) || (!opts.containsKey(PROP_PW)))) {
|
||||
String configUser = _context.getProperty(PROP_USER);
|
||||
String configPW = _context.getProperty(PROP_PW);
|
||||
if (configUser != null && configPW != null) {
|
||||
options.setProperty("i2cp.username", configUser);
|
||||
options.setProperty("i2cp.password", configPW);
|
||||
options.setProperty(PROP_USER, configUser);
|
||||
options.setProperty(PROP_PW, configPW);
|
||||
}
|
||||
}
|
||||
if (options.getProperty(I2PClient.PROP_FAST_RECEIVE) == null)
|
||||
@ -399,6 +416,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
loop = false;
|
||||
break;
|
||||
case OPENING:
|
||||
case GOTDATE:
|
||||
wasOpening = true;
|
||||
try {
|
||||
_stateLock.wait(10*1000);
|
||||
@ -455,6 +473,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
out.write(I2PClient.PROTOCOL_BYTE);
|
||||
out.flush();
|
||||
_writer = new ClientWriterRunner(out, this);
|
||||
_writer.startWriting();
|
||||
InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE);
|
||||
_reader = new I2CPMessageReader(in, this);
|
||||
}
|
||||
@ -462,26 +481,23 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "before startReading");
|
||||
_reader.startReading();
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before getDate");
|
||||
sendMessage(new GetDateMessage(CoreVersion.VERSION));
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After getDate / begin waiting for a response");
|
||||
int waitcount = 0;
|
||||
while (!_dateReceived) {
|
||||
if (waitcount++ > 30) {
|
||||
throw new IOException("No handshake received from the router");
|
||||
}
|
||||
synchronized (_dateReceivedLock) {
|
||||
// InterruptedException caught below
|
||||
_dateReceivedLock.wait(1000);
|
||||
}
|
||||
Properties auth = null;
|
||||
if ((!_context.isRouterContext()) && _options.containsKey(PROP_USER) && _options.containsKey(PROP_PW)) {
|
||||
// Only supported by routers 0.9.10 or higher, but we don't know the version yet.
|
||||
// Auth will also be sent in the SessionConfig.
|
||||
auth = new OrderedProperties();
|
||||
auth.setProperty(PROP_USER, _options.getProperty(PROP_USER));
|
||||
auth.setProperty(PROP_PW, _options.getProperty(PROP_PW));
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After received a SetDate response");
|
||||
sendMessage(new GetDateMessage(CoreVersion.VERSION, auth));
|
||||
waitForDate();
|
||||
|
||||
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()");
|
||||
|
||||
// wait until we have created a lease set
|
||||
waitcount = 0;
|
||||
int 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.");
|
||||
@ -524,6 +540,28 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.10 moved from connect()
|
||||
*/
|
||||
protected void waitForDate() throws InterruptedException, IOException {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After getDate / begin waiting for a response");
|
||||
int waitcount = 0;
|
||||
while (true) {
|
||||
if (waitcount++ > 30) {
|
||||
throw new IOException("No handshake received from the router");
|
||||
}
|
||||
synchronized(_stateLock) {
|
||||
if (_state == State.GOTDATE)
|
||||
break;
|
||||
if (_state != State.OPENING)
|
||||
throw new IOException("Socket closed");
|
||||
// InterruptedException caught by caller
|
||||
_stateLock.wait(1000);
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After received a SetDate response");
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the unencrypted data from the message that we've already prefetched and
|
||||
* notified the user that its available.
|
||||
@ -890,13 +928,16 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
* Will interrupt a connect in progress.
|
||||
*/
|
||||
protected void disconnect() {
|
||||
State oldState;
|
||||
synchronized(_stateLock) {
|
||||
if (_state == State.CLOSING || _state == State.CLOSED)
|
||||
return;
|
||||
oldState = _state;
|
||||
changeState(State.CLOSING);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Disconnect() called", new Exception("Disconnect"));
|
||||
if (shouldReconnect()) {
|
||||
// don't try to reconnect if it failed before GETTDATE
|
||||
if (oldState != State.OPENING && shouldReconnect()) {
|
||||
if (reconnect()) {
|
||||
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "I2CP reconnection successful");
|
||||
return;
|
||||
@ -963,14 +1004,17 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** called by the message handler */
|
||||
/**
|
||||
* Called by the message handler
|
||||
* on reception of DestReplyMessage
|
||||
*/
|
||||
void destReceived(Destination d) {
|
||||
Hash h = d.calculateHash();
|
||||
synchronized (_lookupCache) {
|
||||
_lookupCache.put(h, d);
|
||||
}
|
||||
for (LookupWaiter w : _pendingLookups) {
|
||||
if (w.hash.equals(h)) {
|
||||
if (h.equals(w.hash)) {
|
||||
w.destination = d;
|
||||
synchronized (w) {
|
||||
w.notifyAll();
|
||||
@ -979,10 +1023,52 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
}
|
||||
}
|
||||
|
||||
/** called by the message handler */
|
||||
/**
|
||||
* Called by the message handler
|
||||
* on reception of DestReplyMessage
|
||||
*/
|
||||
void destLookupFailed(Hash h) {
|
||||
for (LookupWaiter w : _pendingLookups) {
|
||||
if (w.hash.equals(h)) {
|
||||
if (h.equals(w.hash)) {
|
||||
synchronized (w) {
|
||||
w.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the message handler
|
||||
* on reception of HostReplyMessage
|
||||
* @since 0.9.10
|
||||
*/
|
||||
void destReceived(long nonce, Destination d) {
|
||||
// notify by hash
|
||||
destReceived(d);
|
||||
// notify by nonce
|
||||
for (LookupWaiter w : _pendingLookups) {
|
||||
if (nonce == w.nonce) {
|
||||
w.destination = d;
|
||||
if (w.name != null) {
|
||||
synchronized (_lookupCache) {
|
||||
_lookupCache.put(w.name, d);
|
||||
}
|
||||
}
|
||||
synchronized (w) {
|
||||
w.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the message handler
|
||||
* on reception of HostReplyMessage
|
||||
* @since 0.9.10
|
||||
*/
|
||||
void destLookupFailed(long nonce) {
|
||||
for (LookupWaiter w : _pendingLookups) {
|
||||
if (nonce == w.nonce) {
|
||||
synchronized (w) {
|
||||
w.notifyAll();
|
||||
}
|
||||
@ -1003,13 +1089,31 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
* @since 0.8.3
|
||||
*/
|
||||
private static class LookupWaiter {
|
||||
/** the request */
|
||||
/** the request (Hash mode) */
|
||||
public final Hash hash;
|
||||
/** the request (String mode) */
|
||||
public final String name;
|
||||
/** the request (nonce mode) */
|
||||
public final long nonce;
|
||||
/** the reply */
|
||||
public volatile Destination destination;
|
||||
|
||||
public LookupWaiter(Hash h) {
|
||||
this(h, -1);
|
||||
}
|
||||
|
||||
/** @since 0.9.10 */
|
||||
public LookupWaiter(Hash h, long nonce) {
|
||||
this.hash = h;
|
||||
this.name = null;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
/** @since 0.9.10 */
|
||||
public LookupWaiter(String name, long nonce) {
|
||||
this.hash = null;
|
||||
this.name = name;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1037,12 +1141,100 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
if (rv != null)
|
||||
return rv;
|
||||
}
|
||||
if (isClosed())
|
||||
if (isClosed()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Session closed, cannot lookup " + h);
|
||||
return null;
|
||||
LookupWaiter waiter = new LookupWaiter(h);
|
||||
}
|
||||
LookupWaiter waiter;
|
||||
long nonce;
|
||||
if (_routerSupportsHostLookup) {
|
||||
nonce = _lookupID.incrementAndGet() & 0x7fffffff;
|
||||
waiter = new LookupWaiter(h, nonce);
|
||||
} else {
|
||||
nonce = 0; // won't be used
|
||||
waiter = new LookupWaiter(h);
|
||||
}
|
||||
_pendingLookups.offer(waiter);
|
||||
try {
|
||||
sendMessage(new DestLookupMessage(h));
|
||||
if (_routerSupportsHostLookup) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending HostLookup for " + h);
|
||||
sendMessage(new HostLookupMessage(h, nonce, maxWait));
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending DestLookup for " + h);
|
||||
sendMessage(new DestLookupMessage(h));
|
||||
}
|
||||
try {
|
||||
synchronized (waiter) {
|
||||
waiter.wait(maxWait);
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
throw new I2PSessionException("Interrupted", ie);
|
||||
}
|
||||
} finally {
|
||||
_pendingLookups.remove(waiter);
|
||||
}
|
||||
return waiter.destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the router to lookup a Destination by host name.
|
||||
* Blocking. Waits a max of 10 seconds by default.
|
||||
*
|
||||
* This only makes sense for a b32 hostname, OR outside router context.
|
||||
* Inside router context, just query the naming service.
|
||||
* Outside router context, this does NOT query the context naming service.
|
||||
* Do that first if you expect a local addressbook.
|
||||
*
|
||||
* This will log a warning for non-b32 in router context.
|
||||
*
|
||||
* See interface for suggested implementation.
|
||||
*
|
||||
* Requires router side to be 0.9.10 or higher. If the router is older,
|
||||
* this will return null immediately.
|
||||
*
|
||||
* @since 0.9.10
|
||||
*/
|
||||
public Destination lookupDest(String name) throws I2PSessionException {
|
||||
return lookupDest(name, 10*1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the router to lookup a Destination by host name.
|
||||
* Blocking. See above for details.
|
||||
* @param maxWait ms
|
||||
* @since 0.9.10
|
||||
* @return null on failure
|
||||
*/
|
||||
public Destination lookupDest(String name, long maxWait) throws I2PSessionException {
|
||||
synchronized (_lookupCache) {
|
||||
Destination rv = _lookupCache.get(name);
|
||||
if (rv != null)
|
||||
return rv;
|
||||
}
|
||||
if (isClosed()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Session closed, cannot lookup " + name);
|
||||
return null;
|
||||
}
|
||||
if (!_routerSupportsHostLookup) {
|
||||
// do them a favor and convert to Hash lookup
|
||||
if (name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p"))
|
||||
return lookupDest(Hash.create(Base32.decode(name.toLowerCase(Locale.US).substring(0, 52))), maxWait);
|
||||
// else unsupported
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Router does not support HostLookup for " + name);
|
||||
return null;
|
||||
}
|
||||
int nonce = _lookupID.incrementAndGet() & 0x7fffffff;
|
||||
LookupWaiter waiter = new LookupWaiter(name, nonce);
|
||||
_pendingLookups.offer(waiter);
|
||||
try {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending HostLookup for " + name);
|
||||
sendMessage(new HostLookupMessage(name, nonce, maxWait));
|
||||
try {
|
||||
synchronized (waiter) {
|
||||
waiter.wait(maxWait);
|
||||
|
@ -14,13 +14,20 @@ import java.net.UnknownHostException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.i2cp.BandwidthLimitsMessage;
|
||||
import net.i2p.data.i2cp.DestReplyMessage;
|
||||
import net.i2p.data.i2cp.DisconnectMessage;
|
||||
import net.i2p.data.i2cp.GetDateMessage;
|
||||
import net.i2p.data.i2cp.HostReplyMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageReader;
|
||||
import net.i2p.data.i2cp.SetDateMessage;
|
||||
import net.i2p.internal.InternalClientManager;
|
||||
import net.i2p.internal.QueuedI2CPMessageReader;
|
||||
import net.i2p.util.I2PSSLSocketFactory;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
|
||||
/**
|
||||
* Create a new session for doing naming and bandwidth queries only. Do not create a Destination.
|
||||
@ -68,6 +75,7 @@ class I2PSimpleSession extends I2PSessionImpl2 {
|
||||
// the following may throw an I2PSessionException
|
||||
_queue = mgr.connect();
|
||||
_reader = new QueuedI2CPMessageReader(_queue, this);
|
||||
_reader.startReading();
|
||||
} else {
|
||||
if (Boolean.parseBoolean(getOptions().getProperty(PROP_ENABLE_SSL))) {
|
||||
try {
|
||||
@ -85,14 +93,47 @@ class I2PSimpleSession extends I2PSessionImpl2 {
|
||||
out.write(I2PClient.PROTOCOL_BYTE);
|
||||
out.flush();
|
||||
_writer = new ClientWriterRunner(out, this);
|
||||
_writer.startWriting();
|
||||
InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE);
|
||||
_reader = new I2CPMessageReader(in, this);
|
||||
_reader.startReading();
|
||||
}
|
||||
}
|
||||
// must be out of synch block for writer to get unblocked
|
||||
if (!_context.isRouterContext()) {
|
||||
Properties opts = getOptions();
|
||||
// Send auth message if required
|
||||
// Auth was not enforced on a simple session until 0.9.10
|
||||
// We will get disconnected for router version < 0.9.10 since it doesn't
|
||||
// support the AuthMessage
|
||||
if ((!opts.containsKey(PROP_USER)) && (!opts.containsKey(PROP_PW))) {
|
||||
// auto-add auth if not set in the options
|
||||
String configUser = _context.getProperty(PROP_USER);
|
||||
String configPW = _context.getProperty(PROP_PW);
|
||||
if (configUser != null && configPW != null) {
|
||||
opts.setProperty(PROP_USER, configUser);
|
||||
opts.setProperty(PROP_PW, configPW);
|
||||
}
|
||||
}
|
||||
if (opts.containsKey(PROP_USER) && opts.containsKey(PROP_PW)) {
|
||||
Properties auth = new OrderedProperties();
|
||||
auth.setProperty(PROP_USER, opts.getProperty(PROP_USER));
|
||||
auth.setProperty(PROP_PW, opts.getProperty(PROP_PW));
|
||||
sendMessage(new GetDateMessage(CoreVersion.VERSION, auth));
|
||||
} else {
|
||||
// we must now send a GetDate even in SimpleSession, or we won't know
|
||||
// what version we are talking with and cannot use HostLookup
|
||||
sendMessage(new GetDateMessage(CoreVersion.VERSION));
|
||||
}
|
||||
waitForDate();
|
||||
}
|
||||
// we do not receive payload messages, so we do not need an AvailabilityNotifier
|
||||
// ... or an Idle timer, or a VerifyUsage
|
||||
_reader.startReading();
|
||||
success = true;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix() + " simple session connected");
|
||||
} 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) {
|
||||
@ -115,9 +156,15 @@ class I2PSimpleSession extends I2PSessionImpl2 {
|
||||
private static class SimpleMessageHandlerMap extends I2PClientMessageHandlerMap {
|
||||
public SimpleMessageHandlerMap(I2PAppContext context) {
|
||||
int highest = Math.max(DestReplyMessage.MESSAGE_TYPE, BandwidthLimitsMessage.MESSAGE_TYPE);
|
||||
highest = Math.max(highest, DisconnectMessage.MESSAGE_TYPE);
|
||||
highest = Math.max(highest, HostReplyMessage.MESSAGE_TYPE);
|
||||
highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE);
|
||||
_handlers = new I2CPMessageHandler[highest+1];
|
||||
_handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context);
|
||||
_handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context);
|
||||
_handlers[DisconnectMessage.MESSAGE_TYPE] = new DisconnectMessageHandler(context);
|
||||
_handlers[HostReplyMessage.MESSAGE_TYPE] = new HostReplyMessageHandler(context);
|
||||
_handlers[SetDateMessage.MESSAGE_TYPE] = new SetDateMessageHandler(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,9 @@ import net.i2p.data.Hash;
|
||||
class LookupDest {
|
||||
|
||||
private static final long DEFAULT_TIMEOUT = 15*1000;
|
||||
private static final String PROP_ENABLE_SSL = "i2cp.SSL";
|
||||
private static final String PROP_USER = "i2cp.username";
|
||||
private static final String PROP_PW = "i2cp.password";
|
||||
|
||||
protected LookupDest(I2PAppContext context) {}
|
||||
|
||||
@ -61,12 +64,23 @@ class LookupDest {
|
||||
Destination rv = null;
|
||||
I2PClient client = new I2PSimpleClient();
|
||||
Properties opts = new Properties();
|
||||
String s = ctx.getProperty(I2PClient.PROP_TCP_HOST);
|
||||
if (s != null)
|
||||
opts.put(I2PClient.PROP_TCP_HOST, s);
|
||||
s = ctx.getProperty(I2PClient.PROP_TCP_PORT);
|
||||
if (s != null)
|
||||
opts.put(I2PClient.PROP_TCP_PORT, s);
|
||||
if (!ctx.isRouterContext()) {
|
||||
String s = ctx.getProperty(I2PClient.PROP_TCP_HOST);
|
||||
if (s != null)
|
||||
opts.put(I2PClient.PROP_TCP_HOST, s);
|
||||
s = ctx.getProperty(I2PClient.PROP_TCP_PORT);
|
||||
if (s != null)
|
||||
opts.put(I2PClient.PROP_TCP_PORT, s);
|
||||
s = ctx.getProperty(PROP_ENABLE_SSL);
|
||||
if (s != null)
|
||||
opts.put(PROP_ENABLE_SSL, s);
|
||||
s = ctx.getProperty(PROP_USER);
|
||||
if (s != null)
|
||||
opts.put(PROP_USER, s);
|
||||
s = ctx.getProperty(PROP_PW);
|
||||
if (s != null)
|
||||
opts.put(PROP_PW, s);
|
||||
}
|
||||
I2PSession session = null;
|
||||
try {
|
||||
session = client.createSession(null, opts);
|
||||
|
@ -12,19 +12,24 @@ package net.i2p.data.i2cp;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
|
||||
/**
|
||||
* Request the other side to send us what they think the current time is/
|
||||
* Request the other side to send us what they think the current time is.
|
||||
* Only supported from client to router.
|
||||
*
|
||||
* Since 0.8.7, optionally include a version string.
|
||||
* Since 0.9.10, optionally include options.
|
||||
*/
|
||||
public class GetDateMessage extends I2CPMessageImpl {
|
||||
public final static int MESSAGE_TYPE = 32;
|
||||
private String _version;
|
||||
private Properties _options;
|
||||
|
||||
public GetDateMessage() {
|
||||
super();
|
||||
@ -39,6 +44,21 @@ public class GetDateMessage extends I2CPMessageImpl {
|
||||
_version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param version the client's version String to be sent to the router; may be null;
|
||||
* must be non-null if options is non-null and non-empty.
|
||||
* @param options Client options to be sent to the router; primarily for authentication; may be null;
|
||||
* keys and values 255 bytes (not chars) max each
|
||||
* @since 0.9.10
|
||||
*/
|
||||
public GetDateMessage(String version, Properties options) {
|
||||
super();
|
||||
if (version == null && options != null && !options.isEmpty())
|
||||
throw new IllegalArgumentException();
|
||||
_version = version;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return may be null
|
||||
* @since 0.8.7
|
||||
@ -47,11 +67,24 @@ public class GetDateMessage extends I2CPMessageImpl {
|
||||
return _version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve any configuration options for the connection.
|
||||
* Primarily for authentication.
|
||||
*
|
||||
* @return may be null
|
||||
* @since 0.9.10
|
||||
*/
|
||||
public Properties getOptions() {
|
||||
return _options;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
|
||||
if (size > 0) {
|
||||
try {
|
||||
_version = DataHelper.readString(in);
|
||||
if (size > 1 + _version.length()) // assume ascii
|
||||
_options = DataHelper.readProperties(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2CPMessageException("Bad version string", dfe);
|
||||
}
|
||||
@ -62,9 +95,11 @@ public class GetDateMessage extends I2CPMessageImpl {
|
||||
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
|
||||
if (_version == null)
|
||||
return new byte[0];
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(16);
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(_options != null ? 128 : 16);
|
||||
try {
|
||||
DataHelper.writeString(os, _version);
|
||||
if (_options != null && !_options.isEmpty())
|
||||
DataHelper.writeProperties(os, _options, true); // UTF-8
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2CPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
@ -80,6 +115,16 @@ public class GetDateMessage extends I2CPMessageImpl {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("[GetDateMessage]");
|
||||
buf.append("\n\tVersion: ").append(_version);
|
||||
if (_options != null && !_options.isEmpty()) {
|
||||
buf.append("\n\tOptions: #: ").append(_options.size());
|
||||
Properties sorted = new OrderedProperties();
|
||||
sorted.putAll(_options);
|
||||
for (Map.Entry<Object, Object> e : sorted.entrySet()) {
|
||||
String key = (String) e.getKey();
|
||||
String val = (String) e.getValue();
|
||||
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
168
core/java/src/net/i2p/data/i2cp/HostLookupMessage.java
Normal file
168
core/java/src/net/i2p/data/i2cp/HostLookupMessage.java
Normal file
@ -0,0 +1,168 @@
|
||||
package net.i2p.data.i2cp;
|
||||
|
||||
/*
|
||||
* Released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
|
||||
/**
|
||||
* Request the router look up the dest for a hash
|
||||
* or a host. Replaces DestLookupMessage.
|
||||
*
|
||||
* @since 0.9.10; do not send to routers older than 0.9.10.
|
||||
*/
|
||||
public class HostLookupMessage extends I2CPMessageImpl {
|
||||
public final static int MESSAGE_TYPE = 38;
|
||||
|
||||
private long _reqID;
|
||||
private long _timeout;
|
||||
private int _lookupType;
|
||||
private Hash _hash;
|
||||
private String _host;
|
||||
|
||||
public static final int LOOKUP_HASH = 0;
|
||||
public static final int LOOKUP_HOST = 1;
|
||||
|
||||
private static final long MAX_INT = (1L << 32) - 1;
|
||||
|
||||
public HostLookupMessage() {}
|
||||
|
||||
/**
|
||||
* @param reqID 0 to 2**32 - 1
|
||||
* @param timeout ms 1 to 2**32 - 1
|
||||
*/
|
||||
public HostLookupMessage(Hash h, long reqID, long timeout) {
|
||||
if (reqID < 0 || reqID > MAX_INT)
|
||||
throw new IllegalArgumentException();
|
||||
if (timeout <= 0 || timeout > MAX_INT)
|
||||
throw new IllegalArgumentException();
|
||||
_hash = h;
|
||||
_reqID = reqID;
|
||||
_timeout = timeout;
|
||||
_lookupType = LOOKUP_HASH;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param reqID 0 to 2**32 - 1
|
||||
* @param timeout ms 1 to 2**32 - 1
|
||||
*/
|
||||
public HostLookupMessage(String host, long reqID, long timeout) {
|
||||
if (reqID < 0 || reqID > MAX_INT)
|
||||
throw new IllegalArgumentException();
|
||||
if (timeout <= 0 || timeout > MAX_INT)
|
||||
throw new IllegalArgumentException();
|
||||
_host = host;
|
||||
_reqID = reqID;
|
||||
_timeout = timeout;
|
||||
_lookupType = LOOKUP_HOST;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 to 2**32 - 1
|
||||
*/
|
||||
public long getReqID() {
|
||||
return _reqID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ms 1 to 2**32 - 1
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return _timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 (hash) or 1 (host)
|
||||
*/
|
||||
public int getLookupType() {
|
||||
return _lookupType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return only valid if lookup type == 0
|
||||
*/
|
||||
public Hash getHash() {
|
||||
return _hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return only valid if lookup type == 1
|
||||
*/
|
||||
public String getHostname() {
|
||||
return _host;
|
||||
}
|
||||
|
||||
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
|
||||
try {
|
||||
_reqID = DataHelper.readLong(in, 4);
|
||||
_timeout = DataHelper.readLong(in, 4);
|
||||
_lookupType = (int) DataHelper.readLong(in, 1);
|
||||
if (_lookupType == LOOKUP_HASH) {
|
||||
_hash = Hash.create(in);
|
||||
} else if (_lookupType == LOOKUP_HOST) {
|
||||
_host = DataHelper.readString(in);
|
||||
if (_host.length() == 0)
|
||||
throw new I2CPMessageException("bad host");
|
||||
} else {
|
||||
throw new I2CPMessageException("bad type");
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2CPMessageException("bad data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
|
||||
int len;
|
||||
if (_lookupType == LOOKUP_HASH) {
|
||||
if (_hash == null)
|
||||
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
|
||||
len = 9 + Hash.HASH_LENGTH;
|
||||
} else if (_lookupType == LOOKUP_HOST) {
|
||||
if (_host == null)
|
||||
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
|
||||
len = 10 + _host.length();
|
||||
} else {
|
||||
throw new I2CPMessageException("bad type");
|
||||
}
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(len);
|
||||
try {
|
||||
DataHelper.writeLong(os, 4, _reqID);
|
||||
DataHelper.writeLong(os, 4, _timeout);
|
||||
DataHelper.writeLong(os, 1, _lookupType);
|
||||
if (_lookupType == LOOKUP_HASH) {
|
||||
_hash.writeBytes(os);
|
||||
} else {
|
||||
DataHelper.writeString(os, _host);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2CPMessageException("bad data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return MESSAGE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("[HostLookupMessage: ");
|
||||
buf.append("\n\tReqID: ").append(_reqID);
|
||||
buf.append("\n\tTimeout: ").append(_timeout);
|
||||
if (_lookupType == LOOKUP_HASH)
|
||||
buf.append("\n\tHash: ").append(_hash);
|
||||
else if (_lookupType == LOOKUP_HOST)
|
||||
buf.append("\n\tHost: ").append(_host);
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
133
core/java/src/net/i2p/data/i2cp/HostReplyMessage.java
Normal file
133
core/java/src/net/i2p/data/i2cp/HostReplyMessage.java
Normal file
@ -0,0 +1,133 @@
|
||||
package net.i2p.data.i2cp;
|
||||
|
||||
/*
|
||||
* Released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
* Response to HostLookupMessage. Replaces DestReplyMessage.
|
||||
*
|
||||
* @since 0.9.10
|
||||
*/
|
||||
public class HostReplyMessage extends I2CPMessageImpl {
|
||||
public final static int MESSAGE_TYPE = 39;
|
||||
|
||||
private Destination _dest;
|
||||
private long _reqID;
|
||||
private int _code;
|
||||
|
||||
public static final int RESULT_SUCCESS = 0;
|
||||
/** generic fail, other codes TBD */
|
||||
public static final int RESULT_FAILURE = 1;
|
||||
|
||||
private static final long MAX_INT = (1L << 32) - 1;
|
||||
|
||||
public HostReplyMessage() {}
|
||||
|
||||
/**
|
||||
* A message with RESULT_SUCCESS and a non-null Destination.
|
||||
*
|
||||
* @param d non-null
|
||||
* @param reqID 0 to 2**32 - 1
|
||||
*/
|
||||
public HostReplyMessage(Destination d, long reqID) {
|
||||
if (d == null)
|
||||
throw new IllegalArgumentException();
|
||||
if (reqID < 0 || reqID > MAX_INT)
|
||||
throw new IllegalArgumentException();
|
||||
_dest = d;
|
||||
_reqID = reqID;
|
||||
}
|
||||
|
||||
/**
|
||||
* A message with a failure code and no Destination.
|
||||
*
|
||||
* @param failureCode 1-255
|
||||
* @param reqID from the HostLookup 0 to 2**32 - 1
|
||||
*/
|
||||
public HostReplyMessage(int failureCode, long reqID) {
|
||||
if (failureCode <= 0 || failureCode > 255)
|
||||
throw new IllegalArgumentException();
|
||||
if (reqID < 0 || reqID > MAX_INT)
|
||||
throw new IllegalArgumentException();
|
||||
_code = failureCode;
|
||||
_reqID = reqID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 to 2**32 - 1
|
||||
*/
|
||||
public long getReqID() {
|
||||
return _reqID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 on success, 1-255 on failure
|
||||
*/
|
||||
public int getResultCode() {
|
||||
return _code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-null only if result code is zero
|
||||
*/
|
||||
public Destination getDestination() {
|
||||
return _dest;
|
||||
}
|
||||
|
||||
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
|
||||
try {
|
||||
_reqID = DataHelper.readLong(in, 4);
|
||||
_code = (int) DataHelper.readLong(in, 1);
|
||||
if (_code == RESULT_SUCCESS)
|
||||
_dest = Destination.create(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2CPMessageException("bad data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
|
||||
int len = 5;
|
||||
if (_code == RESULT_SUCCESS) {
|
||||
if (_dest == null)
|
||||
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
|
||||
len += _dest.size();
|
||||
}
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(len);
|
||||
try {
|
||||
DataHelper.writeLong(os, 4, _reqID);
|
||||
DataHelper.writeLong(os, 1, _code);
|
||||
if (_code == RESULT_SUCCESS)
|
||||
_dest.writeBytes(os);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2CPMessageException("bad data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return MESSAGE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("[HostReplyMessage: ");
|
||||
buf.append("\n\tReqID: ").append(_reqID);
|
||||
buf.append("\n\tCode: ").append(_code);
|
||||
if (_code == RESULT_SUCCESS)
|
||||
buf.append("\n\tDestination: ").append(_dest);
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@ -31,7 +31,7 @@ public class I2CPMessageHandler {
|
||||
* message - if it is an unknown type or has improper formatting, etc.
|
||||
*/
|
||||
public static I2CPMessage readMessage(InputStream in) throws IOException, I2CPMessageException {
|
||||
int length = -1;
|
||||
int length;
|
||||
try {
|
||||
length = (int) DataHelper.readLong(in, 4);
|
||||
} catch (DataFormatException dfe) {
|
||||
@ -41,6 +41,10 @@ public class I2CPMessageHandler {
|
||||
if (length < 0) throw new I2CPMessageException("Invalid message length specified");
|
||||
int type = (int) DataHelper.readLong(in, 1);
|
||||
I2CPMessage msg = createMessage(type);
|
||||
// Note that the readMessage() calls don't, in general, read and discard
|
||||
// extra data, so we can't add new fields to the end of messages
|
||||
// in a compatible way. And the readers could read beyond the length too.
|
||||
// To fix this we'd have to read into a BAOS/BAIS or use a filter input stream
|
||||
msg.readMessage(in, length, type);
|
||||
return msg;
|
||||
} catch (DataFormatException dfe) {
|
||||
@ -52,7 +56,7 @@ public class I2CPMessageHandler {
|
||||
* Yes, this is fairly ugly, but its the only place it ever happens.
|
||||
*
|
||||
*/
|
||||
private static I2CPMessage createMessage(int type) throws IOException,
|
||||
private static I2CPMessage createMessage(int type) throws
|
||||
I2CPMessageException {
|
||||
switch (type) {
|
||||
case CreateLeaseSetMessage.MESSAGE_TYPE:
|
||||
@ -97,6 +101,10 @@ public class I2CPMessageHandler {
|
||||
return new GetBandwidthLimitsMessage();
|
||||
case BandwidthLimitsMessage.MESSAGE_TYPE:
|
||||
return new BandwidthLimitsMessage();
|
||||
case HostLookupMessage.MESSAGE_TYPE:
|
||||
return new HostLookupMessage();
|
||||
case HostReplyMessage.MESSAGE_TYPE:
|
||||
return new HostReplyMessage();
|
||||
default:
|
||||
throw new I2CPMessageException("The type " + type + " is an unknown I2CP message");
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ public class I2CPMessageReader {
|
||||
* reader
|
||||
*
|
||||
* @param reader I2CPMessageReader to notify
|
||||
* @param error Exception that was thrown
|
||||
* @param error Exception that was thrown, non-null
|
||||
*/
|
||||
public void readError(I2CPMessageReader reader, Exception error);
|
||||
|
||||
|
@ -89,7 +89,8 @@ public class SessionConfig extends DataStructureImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the session with the given options
|
||||
* Configure the session with the given options;
|
||||
* keys and values 255 bytes (not chars) max each
|
||||
*
|
||||
* @param options Properties for this session
|
||||
*/
|
||||
|
Reference in New Issue
Block a user