Files
i2p.i2p/router/java/src/net/i2p/data/i2np/SourceRouteBlock.java
jrandom 393b1d7674 big ol' update to strip out the singletons, replacing them with
a rooted app context.  The core itself has its own I2PAppContext
(see its javadoc for, uh, docs), and the router extends that to
expose the router's singletons.  The main point of this is to
make it so that we can run multiple routers in the same JVM, even
to allow different apps in the same JVM to switch singleton
implementations (e.g. run some routers with one set of profile
calculators, and other routers with a different one).
There is still some work to be done regarding the actual boot up
of multiple routers in a JVM, as well as their configuration,
though the plan is to have the RouterContext override the
I2PAppContext's getProperty/getPropertyNames methods to read from
a config file (seperate ones per context) instead of using the
System.getProperty that the base I2PAppContext uses.
Once the multi-router is working, i'll shim in a VMCommSystem
that doesn't depend upon sockets or threads to read/write (and
that uses configurable message send delays / disconnects / etc,
perhaps using data from the routerContext.getProperty to drive it).
I could hold off until the sim is all working, but there's a
truckload of changes in here and I hate dealing with conflicts ;)
Everything works - I've been running 'er for a while and kicked
the tires a bit, but if you see something amiss, please let me
know.
2004-04-24 11:54:35 +00:00

232 lines
9.1 KiB
Java

package net.i2p.data.i2np;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import net.i2p.crypto.ElGamalAESEngine;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.Certificate;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.DataStructureImpl;
import net.i2p.data.Hash;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Defines a single hop of a source routed message, as usable for building a
* SourceRouteReplyMessage
*
* @author jrandom
*/
public class SourceRouteBlock extends DataStructureImpl {
private final static Log _log = new Log(SourceRouteBlock.class);
private Hash _router;
private byte[] _data;
private SessionKey _key;
private byte[] _tag;
private DeliveryInstructions _decryptedInstructions;
private long _decryptedMessageId;
private Certificate _decryptedCertificate;
private long _decryptedExpiration;
public SourceRouteBlock() {
setRouter(null);
setData(null);
setKey(null);
setTag((byte[])null);
_decryptedInstructions = null;
_decryptedMessageId = -1;
_decryptedCertificate = null;
_decryptedExpiration = -1;
}
/**
* Get the router through which replies using this source route block must
* be sent (as the getData() is encrypted for their eyes only)
*
*/
public Hash getRouter() { return _router; }
public void setRouter(Hash router) { _router= router; }
/**
* Get the encrypted header. After decryption (via ElGamal+AES as defined
* in the data structures spec), this array contains:
* DeliveryInstructions
* 4 byte Integer for a message ID
* Certificate
* Date of expiration for replies
*
*/
public byte[] getData() { return _data; }
private void setData(byte data[]) { _data = data; }
/**
* Retrieve the session key which may be used in conjunction with the tag
* to encrypt a garlic message and send it as a reply to this message.
* The encryption would follow scenario 2 of the ElGamal+AES encryption method
* defined in the data structures spec.
*
*/
public SessionKey getKey() { return _key; }
public void setKey(SessionKey key) { _key = key; }
/**
* Get the tag made available for use in conjunction with the getKey() to
* ElGamal+AES encrypt a garlic message without knowing the public key to
* which the message is destined
*
*/
public byte[] getTag() { return _tag; }
public void setTag(SessionTag tag) { setTag(tag.getData()); }
public void setTag(byte tag[]) {
if ( (tag != null) && (tag.length != SessionTag.BYTE_LENGTH) )
throw new IllegalArgumentException("Tag must be either null or 32 bytes");
_tag = tag;
}
/**
* After decryptData, this contains the delivery instructions for this block
*/
public DeliveryInstructions getDecryptedInstructions() { return _decryptedInstructions; }
/**
* After decryptData, this contains the message ID to be used with this block
*/
public long getDecryptedMessageId() { return _decryptedMessageId; }
/**
* After decryptData, this contains the Certificate 'paying' for the forwarding according to
* this block
*/
public Certificate getDecryptedCertificate() { return _decryptedCertificate; }
/**
* After decryptData, this contains the date after which this block should not be forwarded
*/
public long getDecryptedExpiration() { return _decryptedExpiration; }
/**
* Set the raw data with the formatted and encrypted options specified
*
* @param instructions Where a message bearing this block should be sent
* @param messageId ID of the message for this block (not repeatable)
* @param expiration date after which this block expires
* @param replyThrough Encryption key of the router to whom this block is specified (not
* the router specified in the delivery instructions!)
*
* @throws DataFormatException if the data is invalid or could not be encrypted
*/
public void setData(I2PAppContext ctx, DeliveryInstructions instructions,
long messageId, Certificate cert, long expiration,
PublicKey replyThrough) throws DataFormatException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
_decryptedInstructions = instructions;
_decryptedMessageId = messageId;
_decryptedCertificate = cert;
_decryptedExpiration = expiration;
instructions.writeBytes(baos);
DataHelper.writeLong(baos, 4, messageId);
cert.writeBytes(baos);
DataHelper.writeDate(baos, new Date(expiration));
int paddedSize = 256;
SessionKey sessKey = null;
SessionTag tag = null;
if (instructions.getDelayRequested()) {
// always use a new key if we're delaying, since the reply block may not be used within the
// window of a session
sessKey = ctx.keyGenerator().generateSessionKey();
tag = null;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Delay requested - creating a new session key");
} else {
sessKey = ctx.sessionKeyManager().getCurrentKey(replyThrough);
if (sessKey == null) {
sessKey = ctx.keyGenerator().generateSessionKey();
tag = null;
if (_log.shouldLog(Log.DEBUG))
_log.debug("No delay requested, but no session key is known");
} else {
tag = ctx.sessionKeyManager().consumeNextAvailableTag(replyThrough, sessKey);
}
}
byte encData[] = ctx.elGamalAESEngine().encrypt(baos.toByteArray(), replyThrough,
sessKey, null, tag, paddedSize);
setData(encData);
} catch (IOException ioe) {
throw new DataFormatException("Error writing out the source route block data", ioe);
} catch (DataFormatException dfe) {
throw new DataFormatException("Error writing out the source route block data", dfe);
}
}
public void readBytes(InputStream in) throws DataFormatException, IOException {
_router = new Hash();
_router.readBytes(in);
int size = (int)DataHelper.readLong(in, 2);
_data = new byte[size];
int read = read(in, _data);
if (read != _data.length)
throw new DataFormatException("Incorrect # of bytes read for source route block: " + read);
_key = new SessionKey();
_key.readBytes(in);
_tag = new byte[32];
read = read(in, _tag);
if (read != _tag.length)
throw new DataFormatException("Incorrect # of bytes read for session tag: " + read);
}
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if ( (_router == null) || (_data == null) || (_key == null) || (_tag == null) || (_tag.length != 32) )
throw new DataFormatException("Insufficient data to write");
_router.writeBytes(out);
DataHelper.writeLong(out, 2, _data.length);
out.write(_data);
_key.writeBytes(out);
out.write(_tag);
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof SourceRouteBlock))
return false;
SourceRouteBlock block = (SourceRouteBlock)obj;
return DataHelper.eq(getRouter(), block.getRouter()) &&
DataHelper.eq(getData(), block.getData()) &&
DataHelper.eq(getKey(), block.getKey()) &&
DataHelper.eq(getTag(), block.getTag());
}
public int hashCode() {
return DataHelper.hashCode(getRouter()) +
DataHelper.hashCode(getData()) +
DataHelper.hashCode(getKey()) +
DataHelper.hashCode(getTag());
}
public String toString() {
StringBuffer buf = new StringBuffer(128);
buf.append("[SourceRouteBlock: ");
buf.append("\n\tRouter: ").append(getRouter());
buf.append("\n\tData: ").append(DataHelper.toString(getData(), getData().length));
buf.append("\n\tTag: ").append(DataHelper.toString(getTag(), (getTag() != null ? getTag().length : 0)));
buf.append("\n\tKey: ").append(getKey());
buf.append("]");
return buf.toString();
}
}