package net.i2p.client.streaming; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.util.Map; import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.I2PException; import net.i2p.client.I2PClient; import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.crypto.SigType; import net.i2p.util.Log; /** * Simplify the creation of I2PSession and transient I2P Destination objects if * necessary to create a socket manager. This class is most likely how classes * will begin their use of the socket library. * * For new applications, createDisconnectedManager() is the preferred method. * It is non-blocking and throws on all errors. * All createManager() methods are blocking and return null on error. * * Note that for all methods, host and port arguments are ignored if in RouterContext; * it will connect internally to the router in the JVM. * You cannot connect out from a router JVM to another router. * */ public class I2PSocketManagerFactory { /** * Ignored since 0.9.12, cannot be changed via properties. * @deprecated */ @Deprecated public static final String PROP_MANAGER = "i2p.streaming.manager"; /** * The one and only manager. */ public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.impl.I2PSocketManagerFull"; /** * Create a socket manager using a brand new destination connected to the * I2CP router on the local machine on the default port (7654). * * Blocks for a long time while the router builds tunnels. * The nonblocking createDisconnectedManager() is preferred. * * @return the newly created socket manager, or null if there were errors */ public static I2PSocketManager createManager() { return createManager(getHost(), getPort(), (Properties) System.getProperties().clone()); } /** * Create a socket manager using a brand new destination connected to the * I2CP router on the local machine on the default port (7654). * * Blocks for a long time while the router builds tunnels. * The nonblocking createDisconnectedManager() is preferred. * * @param opts Streaming and I2CP options, may be null * @return the newly created socket manager, or null if there were errors */ public static I2PSocketManager createManager(Properties opts) { return createManager(getHost(), getPort(), opts); } /** * Create a socket manager using a brand new destination connected to the * I2CP router on the specified host and port. * * Blocks for a long time while the router builds tunnels. * The nonblocking createDisconnectedManager() is preferred. * * @param host I2CP host null to use default, ignored if in router context * @param port I2CP port <= 0 to use default, ignored if in router context * @return the newly created socket manager, or null if there were errors */ public static I2PSocketManager createManager(String host, int port) { return createManager(host, port, (Properties) System.getProperties().clone()); } /** * Create a socket manager using a brand new destination connected to the * I2CP router on the given machine reachable through the given port. * * Blocks for a long time while the router builds tunnels. * The nonblocking createDisconnectedManager() is preferred. * * @param i2cpHost I2CP host null to use default, ignored if in router context * @param i2cpPort I2CP port <= 0 to use default, ignored if in router context * @param opts Streaming and I2CP options, may be null * @return the newly created socket manager, or null if there were errors */ public static I2PSocketManager createManager(String i2cpHost, int i2cpPort, Properties opts) { I2PClient client = I2PClientFactory.createClient(); ByteArrayOutputStream keyStream = new ByteArrayOutputStream(1024); try { client.createDestination(keyStream, getSigType(opts)); ByteArrayInputStream in = new ByteArrayInputStream(keyStream.toByteArray()); return createManager(in, i2cpHost, i2cpPort, opts); } catch (IOException ioe) { getLog().error("Error creating the destination for socket manager", ioe); return null; } catch (I2PException ie) { getLog().error("Error creating the destination for socket manager", ie); return null; } } /** * Create a socket manager using the destination loaded from the given private key * stream and connected to the default I2CP host and port. * * Blocks for a long time while the router builds tunnels. * The nonblocking createDisconnectedManager() is preferred. * * @param myPrivateKeyStream private key stream, format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} * or null for a transient destination. Caller must close. * @return the newly created socket manager, or null if there were errors */ public static I2PSocketManager createManager(InputStream myPrivateKeyStream) { return createManager(myPrivateKeyStream, getHost(), getPort(), (Properties) System.getProperties().clone()); } /** * Create a socket manager using the destination loaded from the given private key * stream and connected to the default I2CP host and port. * * Blocks for a long time while the router builds tunnels. * The nonblocking createDisconnectedManager() is preferred. * * @param myPrivateKeyStream private key stream, format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} * or null for a transient destination. Caller must close. * @param opts Streaming and I2CP options, may be null * @return the newly created socket manager, or null if there were errors */ public static I2PSocketManager createManager(InputStream myPrivateKeyStream, Properties opts) { return createManager(myPrivateKeyStream, getHost(), getPort(), opts); } /** * Create a socket manager using the destination loaded from the given private key * stream and connected to the I2CP router on the specified machine on the given * port. * * Blocks for a long time while the router builds tunnels. * The nonblocking createDisconnectedManager() is preferred. * * @param myPrivateKeyStream private key stream, format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} * or null for a transient destination. Caller must close. * @param i2cpHost I2CP host null to use default, ignored if in router context * @param i2cpPort I2CP port <= 0 to use default, ignored if in router context * @param opts Streaming and I2CP options, may be null * @return the newly created socket manager, or null if there were errors */ public static I2PSocketManager createManager(InputStream myPrivateKeyStream, String i2cpHost, int i2cpPort, Properties opts) { try { return createManager(myPrivateKeyStream, i2cpHost, i2cpPort, opts, true); } catch (I2PSessionException ise) { getLog().error("Error creating session for socket manager", ise); return null; } } /** * Create a disconnected socket manager using the destination loaded from the given private key * stream, or null for a transient destination. * * Non-blocking. Does not connect to the router or build tunnels. * For servers, caller MUST call getSession().connect() to build tunnels and start listening. * For clients, caller may do that to build tunnels in advance; * otherwise, the first call to connect() will initiate a connection to the router, * with significant delay for tunnel building. * * @param myPrivateKeyStream private key stream, format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} * or null for a transient destination. Caller must close. * @param i2cpHost I2CP host null to use default, ignored if in router context * @param i2cpPort I2CP port <= 0 to use default, ignored if in router context * @param opts Streaming and I2CP options, may be null * @return the newly created socket manager, non-null (throws on error) * @since 0.9.8 */ public static I2PSocketManager createDisconnectedManager(InputStream myPrivateKeyStream, String i2cpHost, int i2cpPort, Properties opts) throws I2PSessionException { if (myPrivateKeyStream == null) { I2PClient client = I2PClientFactory.createClient(); ByteArrayOutputStream keyStream = new ByteArrayOutputStream(1024); try { client.createDestination(keyStream, getSigType(opts)); } catch (I2PException e) { throw new I2PSessionException("Error creating keys", e); } catch (IOException e) { throw new I2PSessionException("Error creating keys", e); } myPrivateKeyStream = new ByteArrayInputStream(keyStream.toByteArray()); } return createManager(myPrivateKeyStream, i2cpHost, i2cpPort, opts, false); } /** * Create a socket manager using the destination loaded from the given private key * stream and connected to the I2CP router on the specified machine on the given * port. * * Blocks for a long time while the router builds tunnels if connect is true. * * @param myPrivateKeyStream private key stream, format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile} * non-null. Caller must close. * @param i2cpHost I2CP host null to use default, ignored if in router context * @param i2cpPort I2CP port <= 0 to use default, ignored if in router context * @param opts Streaming and I2CP options, may be null * @param connect true to connect (blocking) * @return the newly created socket manager, non-null (throws on error) * @since 0.9.7 */ private static I2PSocketManager createManager(InputStream myPrivateKeyStream, String i2cpHost, int i2cpPort, Properties opts, boolean connect) throws I2PSessionException { I2PClient client = I2PClientFactory.createClient(); if (opts == null) opts = new Properties(); Properties syscopy = (Properties) System.getProperties().clone(); for (Map.Entry<Object, Object> e : syscopy.entrySet()) { String name = (String) e.getKey(); if (opts.getProperty(name) == null) opts.setProperty(name, (String) e.getValue()); } // as of 0.8.1 (I2CP default is BestEffort) if (opts.getProperty(I2PClient.PROP_RELIABILITY) == null) opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_NONE); if (i2cpHost != null) opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost); if (i2cpPort > 0) opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort); I2PSession session = client.createSession(myPrivateKeyStream, opts); if (connect) session.connect(); I2PSocketManager sockMgr = createManager(session, opts, "manager"); return sockMgr; } private static I2PSocketManager createManager(I2PSession session, Properties opts, String name) { I2PAppContext context = I2PAppContext.getGlobalContext(); // As of 0.9.12, ignore this setting, as jwebcache and i2phex set it to the old value. // There is no other valid manager. //String classname = opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER); String classname = DEFAULT_MANAGER; try { Class<?> cls = Class.forName(classname); if (!I2PSocketManager.class.isAssignableFrom(cls)) throw new IllegalArgumentException(classname + " is not an I2PSocketManager"); Constructor<?> con = cls.getConstructor(I2PAppContext.class, I2PSession.class, Properties.class, String.class); I2PSocketManager mgr = (I2PSocketManager) con.newInstance(new Object[] {context, session, opts, name}); return mgr; } catch (Throwable t) { getLog().log(Log.CRIT, "Error loading " + classname, t); throw new IllegalStateException(t); } } private static String getHost() { return System.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1"); } private static int getPort() { int i2cpPort = 7654; String i2cpPortStr = System.getProperty(I2PClient.PROP_TCP_PORT); if (i2cpPortStr != null) { try { i2cpPort = Integer.parseInt(i2cpPortStr); } catch (NumberFormatException nfe) { // gobble gobble } } return i2cpPort; } /** * @param opts may be null * @since 0.9.12 */ private static SigType getSigType(Properties opts) { if (opts != null) { String st = opts.getProperty(I2PClient.PROP_SIGTYPE); if (st != null) { SigType rv = SigType.parseSigType(st); if (rv != null && rv.isAvailable()) return rv; if (rv != null) st = rv.toString(); getLog().logAlways(Log.WARN, "Unsupported sig type " + st + ", reverting to " + I2PClient.DEFAULT_SIGTYPE); } } return I2PClient.DEFAULT_SIGTYPE; } /** @since 0.9.7 */ private static Log getLog() { return I2PAppContext.getGlobalContext().logManager().getLog(I2PSocketManagerFactory.class); } }