forked from I2P_Developers/i2p.i2p
386 lines
13 KiB
Java
386 lines
13 KiB
Java
package net.i2p.sam;
|
|
/*
|
|
* free (adj.): unencumbered; not under the control of others
|
|
* Written by human in 2004 and released into the public domain
|
|
* with no warranty of any kind, either expressed or implied.
|
|
* It probably won't make your computer catch on fire, or eat
|
|
* your children, but it might. Use at your own risk.
|
|
*
|
|
*/
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.Closeable;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.util.Properties;
|
|
|
|
import net.i2p.I2PAppContext;
|
|
import net.i2p.client.I2PClient;
|
|
import net.i2p.client.I2PClientFactory;
|
|
import net.i2p.client.I2PSession;
|
|
import net.i2p.client.I2PSessionException;
|
|
import net.i2p.client.I2PSessionMuxedListener;
|
|
import net.i2p.client.SendMessageOptions;
|
|
import net.i2p.data.Base64;
|
|
import net.i2p.data.DataFormatException;
|
|
import net.i2p.data.Destination;
|
|
//import net.i2p.util.HexDump;
|
|
import net.i2p.util.I2PAppThread;
|
|
import net.i2p.util.Log;
|
|
|
|
/**
|
|
* Base abstract class for SAM message-based sessions.
|
|
*
|
|
* @author human
|
|
*/
|
|
abstract class SAMMessageSession implements SAMMessageSess {
|
|
|
|
protected final Log _log;
|
|
private final I2PSession session;
|
|
protected final boolean _isOwnSession;
|
|
private final SAMMessageSessionHandler handler;
|
|
private final int listenProtocol;
|
|
private final int listenPort;
|
|
|
|
/**
|
|
* Initialize a new SAM message-based session.
|
|
*
|
|
* @param dest Base64-encoded destination and private keys (same format as PrivateKeyFile)
|
|
* @param props Properties to setup the I2P session
|
|
* @throws IOException
|
|
* @throws DataFormatException
|
|
* @throws I2PSessionException
|
|
*/
|
|
protected SAMMessageSession(String dest, Properties props) throws IOException, DataFormatException, I2PSessionException {
|
|
this(new ByteArrayInputStream(Base64.decode(dest)), props);
|
|
}
|
|
|
|
/**
|
|
* Initialize a new SAM message-based session.
|
|
*
|
|
* @param destStream Input stream containing the destination and private keys (same format as PrivateKeyFile)
|
|
* @param props Properties to setup the I2P session
|
|
* @throws IOException
|
|
* @throws DataFormatException
|
|
* @throws I2PSessionException
|
|
*/
|
|
protected SAMMessageSession(InputStream destStream, Properties props) throws IOException, DataFormatException, I2PSessionException {
|
|
_log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("Initializing SAM message-based session");
|
|
listenProtocol = I2PSession.PROTO_ANY;
|
|
listenPort = I2PSession.PORT_ANY;
|
|
_isOwnSession = true;
|
|
handler = new SAMMessageSessionHandler(destStream, props);
|
|
session = handler.getSession();
|
|
}
|
|
|
|
/**
|
|
* Initialize a new SAM message-based session using an existing I2PSession.
|
|
*
|
|
* @throws IOException
|
|
* @throws DataFormatException
|
|
* @throws I2PSessionException
|
|
* @since 0.9.25
|
|
*/
|
|
protected SAMMessageSession(I2PSession sess, int listenProtocol, int listenPort)
|
|
throws IOException, DataFormatException, I2PSessionException {
|
|
_log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("Initializing SAM message-based session");
|
|
this.listenProtocol = listenProtocol;
|
|
this.listenPort = listenPort;
|
|
_isOwnSession = false;
|
|
session = sess;
|
|
handler = new SAMMessageSessionHandler(session);
|
|
}
|
|
|
|
/*
|
|
* @since 0.9.25
|
|
*/
|
|
public void start() {
|
|
Thread t = new I2PAppThread(handler, "SAMMessageSessionHandler");
|
|
t.start();
|
|
}
|
|
|
|
/**
|
|
* Get the SAM message-based session Destination.
|
|
*
|
|
* @return The SAM message-based session Destination.
|
|
*/
|
|
public Destination getDestination() {
|
|
return session.getMyDestination();
|
|
}
|
|
|
|
/**
|
|
* @since 0.9.25
|
|
*/
|
|
public int getListenProtocol() {
|
|
return listenProtocol;
|
|
}
|
|
|
|
/**
|
|
* @since 0.9.25
|
|
*/
|
|
public int getListenPort() {
|
|
return listenPort;
|
|
}
|
|
|
|
/**
|
|
* Send bytes through a SAM message-based session.
|
|
*
|
|
* @param dest Destination
|
|
* @param data Bytes to be sent
|
|
*
|
|
* @return True if the data was sent, false otherwise
|
|
* @throws DataFormatException on unknown / bad dest
|
|
* @throws I2PSessionException on serious error, probably session closed
|
|
*/
|
|
public abstract boolean sendBytes(String dest, byte[] data, int proto,
|
|
int fromPort, int toPort) throws DataFormatException, I2PSessionException;
|
|
|
|
/**
|
|
* Actually send bytes through the SAM message-based session I2PSession
|
|
* (er...).
|
|
*
|
|
* @param dest Destination
|
|
* @param data Bytes to be sent
|
|
* @param proto I2CP protocol
|
|
* @param fromPort I2CP from port
|
|
* @param toPort I2CP to port
|
|
*
|
|
* @return True if the data was sent, false otherwise
|
|
* @throws DataFormatException on unknown / bad dest
|
|
* @throws I2PSessionException on serious error, probably session closed
|
|
*/
|
|
protected boolean sendBytesThroughMessageSession(String dest, byte[] data,
|
|
int proto, int fromPort, int toPort)
|
|
throws DataFormatException, I2PSessionException {
|
|
Destination d = SAMUtils.getDest(dest);
|
|
|
|
if (_log.shouldLog(Log.DEBUG)) {
|
|
_log.debug("Sending " + data.length + " bytes to " + dest);
|
|
}
|
|
|
|
return session.sendMessage(d, data, proto, fromPort, toPort);
|
|
}
|
|
|
|
/**
|
|
* Actually send bytes through the SAM message-based session I2PSession,
|
|
* using per-message extended options.
|
|
* For efficiency, use the method without all the extra options if they are all defaults.
|
|
*
|
|
* @param dest Destination
|
|
* @param data Bytes to be sent
|
|
* @param proto I2CP protocol
|
|
* @param fromPort I2CP from port
|
|
* @param toPort I2CP to port
|
|
* @param sendLeaseSet true is the usual setting and the I2CP default
|
|
* @param sendTags 0 to leave as default
|
|
* @param tagThreshold 0 to leave as default
|
|
* @param expiration SECONDS from now, NOT absolute time, 0 to leave as default
|
|
*
|
|
* @return True if the data was sent, false otherwise
|
|
* @throws DataFormatException on unknown / bad dest
|
|
* @throws I2PSessionException on serious error, probably session closed
|
|
* @since 0.9.24
|
|
*/
|
|
protected boolean sendBytesThroughMessageSession(String dest, byte[] data,
|
|
int proto, int fromPort, int toPort,
|
|
boolean sendLeaseSet, int sendTags,
|
|
int tagThreshold, int expiration)
|
|
throws DataFormatException, I2PSessionException {
|
|
Destination d = SAMUtils.getDest(dest);
|
|
|
|
if (_log.shouldLog(Log.DEBUG)) {
|
|
_log.debug("Sending " + data.length + " bytes to " + dest);
|
|
}
|
|
SendMessageOptions opts = new SendMessageOptions();
|
|
if (!sendLeaseSet)
|
|
opts.setSendLeaseSet(false);
|
|
if (sendTags > 0)
|
|
opts.setTagsToSend(sendTags);
|
|
if (tagThreshold > 0)
|
|
opts.setTagThreshold(tagThreshold);
|
|
if (expiration > 0)
|
|
opts.setDate(I2PAppContext.getGlobalContext().clock().now() + (expiration * 1000));
|
|
|
|
return session.sendMessage(d, data, 0, data.length, proto, fromPort, toPort, opts);
|
|
}
|
|
|
|
/**
|
|
* Close a SAM message-based session.
|
|
*/
|
|
public void close() {
|
|
handler.stopRunning();
|
|
}
|
|
|
|
/**
|
|
* Handle a new received message
|
|
* @param msg Message payload
|
|
*/
|
|
protected abstract void messageReceived(byte[] msg, int proto, int fromPort, int toPort);
|
|
|
|
/**
|
|
* Do whatever is needed to shutdown the SAM session
|
|
*/
|
|
protected abstract void shutDown();
|
|
|
|
|
|
/**
|
|
* Get the I2PSession object used by the SAM message-based session.
|
|
*
|
|
* @return The I2PSession of the SAM message-based session
|
|
*/
|
|
protected I2PSession getI2PSession() {
|
|
return session;
|
|
}
|
|
|
|
/**
|
|
* SAM message-based session handler, running in its own thread
|
|
*
|
|
* @author human
|
|
*/
|
|
private class SAMMessageSessionHandler implements Runnable, I2PSessionMuxedListener {
|
|
|
|
private final I2PSession _session;
|
|
private final Object runningLock = new Object();
|
|
private volatile boolean stillRunning = true;
|
|
|
|
/**
|
|
* Create a new SAM message-based session handler
|
|
*
|
|
* @param destStream Input stream containing the destination keys
|
|
* @param props Properties to setup the I2P session
|
|
* @throws I2PSessionException
|
|
*/
|
|
public SAMMessageSessionHandler(InputStream destStream, Properties props) throws I2PSessionException {
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("Instantiating new SAM message-based session handler");
|
|
|
|
I2PClient client = I2PClientFactory.createClient();
|
|
if (!props.containsKey("inbound.nickname") && !props.containsKey("outbound.nickname")) {
|
|
props.setProperty("inbound.nickname", "SAM UDP Client");
|
|
props.setProperty("outbound.nickname", "SAM UDP Client");
|
|
}
|
|
_session = client.createSession(destStream, props);
|
|
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("Connecting I2P session...");
|
|
_session.connect();
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("I2P session connected");
|
|
|
|
_session.addMuxedSessionListener(this, listenProtocol, listenPort);
|
|
}
|
|
|
|
/**
|
|
* Create a new SAM message-based session handler on an existing I2PSession
|
|
*
|
|
* @since 0.9.25
|
|
*/
|
|
public SAMMessageSessionHandler(I2PSession sess) throws I2PSessionException {
|
|
_session = sess;
|
|
_session.addMuxedSessionListener(this, listenProtocol, listenPort);
|
|
}
|
|
|
|
/**
|
|
* The session.
|
|
* @since 0.9.25
|
|
*/
|
|
public final I2PSession getSession() {
|
|
return _session;
|
|
}
|
|
|
|
/**
|
|
* Stop a SAM message-based session handling thread
|
|
*
|
|
*/
|
|
public final void stopRunning() {
|
|
synchronized (runningLock) {
|
|
stillRunning = false;
|
|
runningLock.notify();
|
|
}
|
|
}
|
|
|
|
public void run() {
|
|
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("SAM message-based session handler running");
|
|
|
|
synchronized (runningLock) {
|
|
while (stillRunning) {
|
|
try {
|
|
runningLock.wait();
|
|
} catch (InterruptedException ie) {}
|
|
}
|
|
}
|
|
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("Shutting down SAM message-based session handler");
|
|
|
|
shutDown();
|
|
session.removeListener(listenProtocol, listenPort);
|
|
|
|
if (_isOwnSession) {
|
|
try {
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("Destroying I2P session...");
|
|
session.destroySession();
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("I2P session destroyed");
|
|
} catch (I2PSessionException e) {
|
|
_log.error("Error destroying I2P session", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void disconnected(I2PSession session) {
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("I2P session disconnected");
|
|
stopRunning();
|
|
}
|
|
|
|
public void errorOccurred(I2PSession session, String message,
|
|
Throwable error) {
|
|
if (_log.shouldLog(Log.DEBUG))
|
|
_log.debug("I2P error: " + message, error);
|
|
stopRunning();
|
|
}
|
|
|
|
public void messageAvailable(I2PSession session, int msgId, long size) {
|
|
messageAvailable(session, msgId, size, I2PSession.PROTO_UNSPECIFIED,
|
|
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
|
}
|
|
|
|
/** @since 0.9.24 */
|
|
public void messageAvailable(I2PSession session, int msgId, long size,
|
|
int proto, int fromPort, int toPort) {
|
|
|
|
if (_log.shouldLog(Log.DEBUG)) {
|
|
_log.debug("I2P message available (id: " + msgId
|
|
+ "; size: " + size + ")");
|
|
}
|
|
try {
|
|
byte msg[] = session.receiveMessage(msgId);
|
|
if (msg == null)
|
|
return;
|
|
//if (_log.shouldLog(Log.DEBUG)) {
|
|
// _log.debug("Content of message " + msgId + ":\n"
|
|
// + HexDump.dump(msg));
|
|
//}
|
|
|
|
messageReceived(msg, proto, fromPort, toPort);
|
|
} catch (I2PSessionException e) {
|
|
_log.error("Error fetching I2P message", e);
|
|
stopRunning();
|
|
}
|
|
}
|
|
|
|
public void reportAbuse(I2PSession session, int severity) {
|
|
_log.warn("Abuse reported (severity: " + severity + ")");
|
|
stopRunning();
|
|
}
|
|
}
|
|
}
|