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();
}
}
}