Files
i2p.i2p/router/java/src/net/i2p/data/i2np/SourceRouteBlock.java
2004-05-17 03:38:53 +00:00

229 lines
9.0 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.I2PAppContext;
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;
/**
* 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();
}
}