package net.i2p.router.tunnel;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.data.Hash;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.i2np.BuildRequestRecord;
import net.i2p.data.i2np.EncryptedBuildRecord;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.TunnelBuildMessage;
/**
* Fill in the encrypted BuildRequestRecords in a TunnelBuildMessage
*/
public abstract class BuildMessageGenerator {
/** return null if it is unable to find a router's public key (etc) */
/****
public TunnelBuildMessage createInbound(RouterContext ctx, TunnelCreatorConfig cfg) {
return create(ctx, cfg, null, -1);
}
****/
/** return null if it is unable to find a router's public key (etc) */
/****
public TunnelBuildMessage createOutbound(RouterContext ctx, TunnelCreatorConfig cfg, Hash replyRouter, long replyTunnel) {
return create(ctx, cfg, replyRouter, replyTunnel);
}
****/
/****
private TunnelBuildMessage create(RouterContext ctx, TunnelCreatorConfig cfg, Hash replyRouter, long replyTunnel) {
TunnelBuildMessage msg = new TunnelBuildMessage(ctx);
List order = new ArrayList(ORDER.length);
for (int i = 0; i < ORDER.length; i++) order.add(ORDER[i]);
Collections.shuffle(order, ctx.random());
for (int i = 0; i < ORDER.length; i++) {
int hop = ((Integer)order.get(i)).intValue();
Hash peer = cfg.getPeer(hop);
RouterInfo ri = ctx.netDb().lookupRouterInfoLocally(peer);
if (ri == null)
return null;
createRecord(i, hop, msg, cfg, replyRouter, replyTunnel, ctx, ri.getIdentity().getPublicKey());
}
layeredEncrypt(ctx, msg, cfg, order);
return msg;
}
****/
/**
* Place the asymmetrically encrypted record in the specified record slot,
* containing the hop's configuration (as well as the reply info, if it is an outbound endpoint)
*
* @param msg out parameter
* @param peerKey Encrypt using this key.
* If null, replyRouter and replyTunnel are ignored,
* and the entire record is filled with random data
* @throws IllegalArgumentException if hop bigger than config
*/
public static void createRecord(int recordNum, int hop, TunnelBuildMessage msg,
TunnelCreatorConfig cfg, Hash replyRouter,
long replyTunnel, I2PAppContext ctx, PublicKey peerKey) {
//Log log = ctx.logManager().getLog(BuildMessageGenerator.class);
EncryptedBuildRecord erec;
if (peerKey != null) {
BuildRequestRecord req = null;
if ( (!cfg.isInbound()) && (hop + 1 == cfg.getLength()) ) //outbound endpoint
req = createUnencryptedRecord(ctx, cfg, hop, replyRouter, replyTunnel);
else
req = createUnencryptedRecord(ctx, cfg, hop, null, -1);
if (req == null)
throw new IllegalArgumentException("hop bigger than config");
Hash peer = cfg.getPeer(hop);
//if (log.shouldLog(Log.DEBUG))
// log.debug("Record " + recordNum + "/" + hop + "/" + peer.toBase64()
// + ": unencrypted = " + Base64.encode(req.getData().getData()));
erec = req.encryptRecord(ctx, peerKey, peer);
//if (log.shouldLog(Log.DEBUG))
// log.debug("Record " + recordNum + "/" + hop + ": encrypted = " + Base64.encode(encrypted));
} else {
//if (log.shouldLog(Log.DEBUG))
// log.debug("Record " + recordNum + "/" + hop + "/ is blank/random");
byte encrypted[] = new byte[TunnelBuildMessage.RECORD_SIZE];
ctx.random().nextBytes(encrypted);
erec = new EncryptedBuildRecord(encrypted);
}
msg.setRecord(recordNum, erec);
}
/**
* Returns null if hop >= cfg.length
*/
private static BuildRequestRecord createUnencryptedRecord(I2PAppContext ctx, TunnelCreatorConfig cfg, int hop,
Hash replyRouter, long replyTunnel) {
//Log log = ctx.logManager().getLog(BuildMessageGenerator.class);
if (hop < cfg.getLength()) {
// ok, now lets fill in some data
HopConfig hopConfig = cfg.getConfig(hop);
Hash peer = cfg.getPeer(hop);
long recvTunnelId = -1;
if (cfg.isInbound() || (hop > 0))
recvTunnelId = hopConfig.getReceiveTunnel().getTunnelId();
else
recvTunnelId = 0;
long nextTunnelId = -1;
Hash nextPeer = null;
if (hop + 1 < cfg.getLength()) {
nextTunnelId = cfg.getConfig(hop+1).getReceiveTunnel().getTunnelId();
nextPeer = cfg.getPeer(hop+1);
} else {
if ( (replyTunnel >= 0) && (replyRouter != null) ) {
nextTunnelId = replyTunnel;
nextPeer = replyRouter;
} else {
// inbound endpoint (aka creator)
nextTunnelId = 0;
nextPeer = peer; // self
}
}
SessionKey layerKey = hopConfig.getLayerKey();
SessionKey ivKey = hopConfig.getIVKey();
SessionKey replyKey = hopConfig.getReplyKey();
byte iv[] = hopConfig.getReplyIV();
if (iv == null) {
iv = new byte[BuildRequestRecord.IV_SIZE];
ctx.random().nextBytes(iv);
hopConfig.setReplyIV(iv);
}
boolean isInGW = (cfg.isInbound() && (hop == 0));
boolean isOutEnd = (!cfg.isInbound() && (hop + 1 >= cfg.getLength()));
long nextMsgId = -1;
if (isOutEnd || (cfg.isInbound() && (hop + 2 >= cfg.getLength())) ) {
nextMsgId = cfg.getReplyMessageId();
} else {
// dont care about these intermediary hops
nextMsgId = ctx.random().nextLong(I2NPMessage.MAX_ID_VALUE);
}
//if (log.shouldLog(Log.DEBUG))
// log.debug("Hop " + hop + " has the next message ID of " + nextMsgId + " for " + cfg
// + " with replyKey " + replyKey.toBase64() + " and replyIV " + Base64.encode(iv));
BuildRequestRecord rec= new BuildRequestRecord(ctx, recvTunnelId, peer, nextTunnelId, nextPeer,
nextMsgId, layerKey, ivKey, replyKey,
iv, isInGW, isOutEnd);
return rec;
} else {
return null;
}
}
/**
* Encrypt the records so their hop ident is visible at the appropriate times.
*
* Note that this layer-encrypts the build records for the message in-place.
* Only call this once for a given message.
*
* @param order list of hop #s as Integers. For instance, if (order.get(1) is 4), it is peer cfg.getPeer(4)
*/
public static void layeredEncrypt(I2PAppContext ctx, TunnelBuildMessage msg,
TunnelCreatorConfig cfg, List<Integer> order) {
//Log log = ctx.logManager().getLog(BuildMessageGenerator.class);
// encrypt the records so that the right elements will be visible at the right time
for (int i = 0; i < msg.getRecordCount(); i++) {
EncryptedBuildRecord rec = msg.getRecord(i);
Integer hopNum = order.get(i);
int hop = hopNum.intValue();
if ( (isBlank(cfg, hop)) || (!cfg.isInbound() && hop == 1) ) {
//if (log.shouldLog(Log.DEBUG))
// log.debug(msg.getUniqueId() + ": not pre-decrypting record " + i + "/" + hop + " for " + cfg);
continue;
}
//if (log.shouldLog(Log.DEBUG))
// log.debug(msg.getUniqueId() + ": pre-decrypting record " + i + "/" + hop + " for " + cfg);
// ok, now decrypt the record with all of the reply keys from cfg.getConfig(0) through hop-1
int stop = (cfg.isInbound() ? 0 : 1);
for (int j = hop-1; j >= stop; j--) {
HopConfig hopConfig = cfg.getConfig(j);
SessionKey key = hopConfig.getReplyKey();
byte iv[] = hopConfig.getReplyIV();
//if (log.shouldLog(Log.DEBUG))
// log.debug(msg.getUniqueId() + ": pre-decrypting record " + i + "/" + hop + " for " + cfg
// + " with " + key.toBase64() + "/" + Base64.encode(iv));
// corrupts the SDS
ctx.aes().decrypt(rec.getData(), 0, rec.getData(), 0, key, iv, TunnelBuildMessage.RECORD_SIZE);
}
}
//if (log.shouldLog(Log.DEBUG))
// log.debug(msg.getUniqueId() + ": done pre-decrypting all records for " + cfg);
}
public static boolean isBlank(TunnelCreatorConfig cfg, int hop) {
if (cfg.isInbound()) {
if (hop + 1 >= cfg.getLength())
return true;
else
return false;
} else {
if (hop == 0)
return true;
else if (hop >= cfg.getLength())
return true;
else
return false;
}
}
}