package net.i2p.client.impl;
/*
* Released into the public domain
* with no warranty of any kind, either expressed or implied.
*/
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.security.GeneralSecurityException;
import java.util.Properties;
import net.i2p.CoreVersion;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSessionException;
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;
import net.i2p.util.SystemVersion;
/**
* Create a new session for doing naming and bandwidth queries only. Do not create a Destination.
* Don't create a producer. Do not send/receive messages to other Destinations.
* Cannot handle multiple simultaneous queries atm.
* Could be expanded to ask the router other things.
*
* @author zzz
*/
public class I2PSimpleSession extends I2PSessionImpl2 {
private static final int BUF_SIZE = 1024;
/**
* Create a new session for doing naming and bandwidth queries only. Do not create a destination.
*
* @throws I2PSessionException if there is a problem
*/
public I2PSimpleSession(I2PAppContext context, Properties options) throws I2PSessionException {
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 {
changeState(State.OPENING);
boolean success = false;
try {
// 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);
_reader.startReading();
} else {
if (Boolean.parseBoolean(getOptions().getProperty(PROP_ENABLE_SSL))) {
try {
I2PSSLSocketFactory fact = new I2PSSLSocketFactory(_context, false, "certificates/i2cp");
_socket = fact.createSocket(_hostname, _portNum);
} catch (GeneralSecurityException gse) {
IOException ioe = new IOException("SSL Fail");
ioe.initCause(gse);
throw ioe;
}
} else {
_socket = new Socket(_hostname, _portNum);
}
_socket.setKeepAlive(true);
OutputStream out = _socket.getOutputStream();
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.11
// We will get disconnected for router version < 0.9.11 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_unchecked(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_unchecked(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
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) {
// Generate the best error message as this will be logged
String msg;
if (_context.isRouterContext())
msg = "Failed internal router binding";
else if (SystemVersion.isAndroid() &&
Boolean.parseBoolean(getOptions().getProperty(PROP_DOMAIN_SOCKET)))
msg = "Failed to bind to the router";
else
msg = "Cannot connect to the router on " + _hostname + ':' + _portNum;
throw new I2PSessionException(getPrefix() + msg, ioe);
} finally {
changeState(success ? State.OPEN : State.CLOSED);
}
}
/**
* Ignore, does nothing
* @since 0.8.4
*/
@Override
public void updateOptions(Properties options) {}
/**
* Only map message handlers that we will use
*/
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);
}
}
}