diff --git a/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java b/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java index 6ae986d9e48008d8638d4f552511208348ad254d..50f6f6b6a11a313fe27144c05b6ba1b294910dbc 100644 --- a/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java +++ b/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java @@ -69,7 +69,11 @@ public class InboundTunnelBuildMessage extends TunnelBuildMessage { } @Override - protected int calculateWrittenLength() { return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE); } + protected int calculateWrittenLength() { + if (_plaintextRecord == null) + throw new IllegalStateException("Plaintext record not set"); + return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE); + } @Override public int getType() { return MESSAGE_TYPE; } @@ -108,6 +112,8 @@ public class InboundTunnelBuildMessage extends TunnelBuildMessage { @Override protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException { + if (_plaintextRecord == null) + throw new I2NPMessageException("Plaintext record not set"); int remaining = out.length - (curIndex + calculateWrittenLength()); if (remaining < 0) throw new I2NPMessageException("Not large enough (too short by " + remaining + ")"); diff --git a/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java b/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java index da00dc495070e15b6798143508bc16d8032e2e92..fc0b352622542797be1a7c43cbd1140a9d8d3427 100644 --- a/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java +++ b/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java @@ -128,7 +128,11 @@ public class OutboundTunnelBuildReplyMessage extends TunnelBuildReplyMessage { } @Override - protected int calculateWrittenLength() { return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE); } + protected int calculateWrittenLength() { + if (_plaintextRecord == null) + throw new IllegalStateException("Plaintext record not set"); + return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE); + } @Override public int getType() { return MESSAGE_TYPE; } @@ -167,6 +171,8 @@ public class OutboundTunnelBuildReplyMessage extends TunnelBuildReplyMessage { @Override protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException { + if (_plaintextRecord == null) + throw new I2NPMessageException("Plaintext record not set"); int remaining = out.length - (curIndex + calculateWrittenLength()); if (remaining < 0) throw new I2NPMessageException("Not large enough (too short by " + remaining + ")"); diff --git a/router/java/src/net/i2p/data/i2np/ShortTunnelBuildReplyMessage.java b/router/java/src/net/i2p/data/i2np/ShortTunnelBuildReplyMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..36468eea9ebd31902b0026907f9ccea4c2937521 --- /dev/null +++ b/router/java/src/net/i2p/data/i2np/ShortTunnelBuildReplyMessage.java @@ -0,0 +1,64 @@ +package net.i2p.data.i2np; + +import net.i2p.I2PAppContext; + +/** + * Internal use only, to convert an inbound STBM to a reply. + * Never serialized/deserialized/sent/received. + * See BuildHandler and BuildReplyHandler. + * + * @since 0.9.51 + */ +public class ShortTunnelBuildReplyMessage extends TunnelBuildReplyMessage { + /** + * Impossible value, more than 1 byte + */ + public static final int MESSAGE_TYPE = 999; + public static final int SHORT_RECORD_SIZE = ShortTunnelBuildMessage.SHORT_RECORD_SIZE; + + public ShortTunnelBuildReplyMessage(I2PAppContext context, int records) { + super(context, records); + } + + /** + * @param record must be ShortEncryptedBuildRecord or null + */ + @Override + public void setRecord(int index, EncryptedBuildRecord record) { + if (record != null && record.length() != SHORT_RECORD_SIZE) + throw new IllegalArgumentException(); + super.setRecord(index, record); + } + + @Override + protected int calculateWrittenLength() { return 0; } + + @Override + public int getType() { return MESSAGE_TYPE; } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + buf.append("[ShortTunnelBuildReplyMessage: " + + "\n\tID: ").append(getUniqueId()) + .append("\n\tRecords: ").append(getRecordCount()) + .append(']'); + return buf.toString(); + } +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java index 1c44dc6d00bf19ed2abcbbda6fdf820e433ea940..87bf37c3ce7d56d1ce14bbbc6f28fa9a3a098ffa 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java @@ -22,6 +22,7 @@ import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.i2np.InboundTunnelBuildMessage; import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage; import net.i2p.data.i2np.ShortTunnelBuildMessage; +import net.i2p.data.i2np.ShortTunnelBuildReplyMessage; import net.i2p.data.i2np.TunnelBuildMessage; import net.i2p.data.i2np.TunnelBuildReplyMessage; import net.i2p.data.i2np.TunnelGatewayMessage; @@ -566,12 +567,15 @@ class BuildHandler implements Runnable { private void handleRequestAsInboundEndpoint(BuildEndMessageState state) { int records = state.msg.getRecordCount(); TunnelBuildReplyMessage msg; - if (records == TunnelBuildMessage.MAX_RECORD_COUNT) + if (state.msg.getType() == ShortTunnelBuildMessage.MESSAGE_TYPE) + msg = new ShortTunnelBuildReplyMessage(_context, records); + else if (records == TunnelBuildMessage.MAX_RECORD_COUNT) msg = new TunnelBuildReplyMessage(_context); else msg = new VariableTunnelBuildReplyMessage(_context, records); - for (int i = 0; i < records; i++) + for (int i = 0; i < records; i++) { msg.setRecord(i, state.msg.getRecord(i)); + } msg.setUniqueId(state.msg.getUniqueId()); handleReply(msg, state.cfg, System.currentTimeMillis() - state.recvTime); } diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildReplyHandler.java b/router/java/src/net/i2p/router/tunnel/pool/BuildReplyHandler.java index 6371ddcd257e0624306cf066b888b4808ca84efa..92726222eb9d158e307c17b6f16722a53cea635d 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildReplyHandler.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildReplyHandler.java @@ -12,6 +12,7 @@ import net.i2p.data.i2np.BuildResponseRecord; import net.i2p.data.i2np.EncryptedBuildRecord; import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage; import net.i2p.data.i2np.ShortEncryptedBuildRecord; +import net.i2p.data.i2np.ShortTunnelBuildReplyMessage; import net.i2p.data.i2np.TunnelBuildReplyMessage; import net.i2p.router.tunnel.TunnelCreatorConfig; import net.i2p.util.Log; @@ -52,7 +53,6 @@ class BuildReplyHandler { log.error("Corrupted build reply, expected " + recordOrder.size() + " records, got " + reply.getRecordCount()); return null; } - boolean isShort = reply.getType() == OutboundTunnelBuildReplyMessage.MESSAGE_TYPE; int rv[] = new int[reply.getRecordCount()]; for (int i = 0; i < rv.length; i++) { int hop = recordOrder.get(i).intValue(); @@ -89,7 +89,7 @@ class BuildReplyHandler { rv[i] = ok; } } - if (isShort) { + if (reply.getType() == OutboundTunnelBuildReplyMessage.MESSAGE_TYPE) { OutboundTunnelBuildReplyMessage otbrm = (OutboundTunnelBuildReplyMessage) reply; rv[otbrm.getPlaintextSlot()] = otbrm.getPlaintextReply(); } @@ -107,9 +107,10 @@ class BuildReplyHandler { */ private int decryptRecord(TunnelBuildReplyMessage reply, TunnelCreatorConfig cfg, int recordNum, int hop) { EncryptedBuildRecord rec = reply.getRecord(recordNum); - boolean isShort = reply.getType() == OutboundTunnelBuildReplyMessage.MESSAGE_TYPE; + int type = reply.getType(); + boolean isOTBRM = type == OutboundTunnelBuildReplyMessage.MESSAGE_TYPE; if (rec == null) { - if (!isShort) { + if (!isOTBRM) { if (log.shouldWarn()) log.warn("Missing record " + recordNum); return -1; @@ -135,6 +136,7 @@ class BuildReplyHandler { if (isEC) end++; // do we need to adjust this for the endpoint? + boolean isShort = isOTBRM || type == ShortTunnelBuildReplyMessage.MESSAGE_TYPE; if (isShort) { byte iv[] = new byte[12]; for (int j = start; j >= end; j--) { diff --git a/router/java/test/junit/net/i2p/router/tunnel/pool/BuildMessageTestStandalone.java b/router/java/test/junit/net/i2p/router/tunnel/pool/BuildMessageTestStandalone.java index fa3efdd1e2b248f7a930e6ef8f7fe1c3c67cc08e..e45897881d18a2653b9bb57094ce46e28fc887a0 100644 --- a/router/java/test/junit/net/i2p/router/tunnel/pool/BuildMessageTestStandalone.java +++ b/router/java/test/junit/net/i2p/router/tunnel/pool/BuildMessageTestStandalone.java @@ -24,8 +24,10 @@ import net.i2p.data.i2np.EncryptedBuildRecord; import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.i2np.I2NPMessageException; import net.i2p.data.i2np.I2NPMessageHandler; +import net.i2p.data.i2np.InboundTunnelBuildMessage; import net.i2p.data.i2np.OutboundTunnelBuildReplyMessage; import net.i2p.data.i2np.ShortTunnelBuildMessage; +import net.i2p.data.i2np.ShortTunnelBuildReplyMessage; import net.i2p.data.i2np.TunnelBuildMessage; import net.i2p.data.i2np.TunnelBuildReplyMessage; import net.i2p.router.Router; @@ -53,18 +55,18 @@ public class BuildMessageTestStandalone extends TestCase { public void testBuildMessage() { RouterContext ctx = new RouterContext(null); - x_testBuildMessage(ctx, 1); - x_testBuildMessage(ctx, 2); - x_testBuildMessage(ctx, 3); + for (int i = 1; i <= 6; i++) { + x_testBuildMessage(ctx, i); + } } /** - * @param testType 1=ElG; 2=ECIES; 3=ECIES short outbound; 4=ECIES short inbound + * @param testType outbound: 1=ElG; 2=ECIES; 3=ECIES short; inbound: 4-6 */ private void x_testBuildMessage(RouterContext ctx, int testType) { Log log = ctx.logManager().getLog(getClass()); // set our keys to avoid NPE - KeyPair kpr = ctx.keyGenerator().generatePKIKeys(testType == 1 ? EncType.ELGAMAL_2048 : EncType.ECIES_X25519); + KeyPair kpr = ctx.keyGenerator().generatePKIKeys((testType == 1 || testType == 4) ? EncType.ELGAMAL_2048 : EncType.ECIES_X25519); PublicKey k1 = kpr.getPublic(); PrivateKey k2 = kpr.getPrivate(); Object[] kp = ctx.keyGenerator().generateSigningKeypair(); @@ -82,16 +84,34 @@ public class BuildMessageTestStandalone extends TestCase { // populate and encrypt the message TunnelBuildMessage msg; - if (testType == 3) + if (testType == 3) { msg = new ShortTunnelBuildMessage(ctx, TunnelBuildMessage.MAX_RECORD_COUNT); - else + } else if (testType == 6) { + InboundTunnelBuildMessage itbm = new InboundTunnelBuildMessage(ctx, TunnelBuildMessage.MAX_RECORD_COUNT); + // set plaintext record for ibgw + for (int i = 0; i < order.size(); i++) { + int hop = order.get(i).intValue(); + if (hop == 0) { + // TODO + itbm.setPlaintextRecord(i, new byte[100]); + break; + } + } + msg = itbm; + } else { msg = new TunnelBuildMessage(ctx); + } + int end = cfg.getLength(); + if (testType > 3) + end--; for (int i = 0; i < order.size(); i++) { int hop = order.get(i).intValue(); PublicKey key = null; - if (hop < _pubKeys.length) + if (hop < end) key = _pubKeys[hop]; - BuildMessageGenerator.createRecord(i, hop, msg, cfg, _replyRouter, _replyTunnel, ctx, key); + // don't do this for ibgw in itbm + if (testType != 6 || hop != 0) + BuildMessageGenerator.createRecord(i, hop, msg, cfg, _replyRouter, _replyTunnel, ctx, key); } BuildMessageGenerator.layeredEncrypt(ctx, msg, cfg, order); @@ -100,14 +120,14 @@ public class BuildMessageTestStandalone extends TestCase { "\n" + cfg + "\n================================================================"); - if (testType == 3) { - // test read/write + if (testType == 3 || testType == 6) { + // test read/write for new messages byte[] data = msg.toByteArray(); try { I2NPMessage msg2 = (new I2NPMessageHandler(ctx)).readMessage(data); - msg = (ShortTunnelBuildMessage) msg2; + msg = (TunnelBuildMessage) msg2; } catch (Exception e) { - log.error("STBM out/in fail", e); + log.error("TBM out/in fail", e); assertTrue(e.toString(), false); } } @@ -115,8 +135,10 @@ public class BuildMessageTestStandalone extends TestCase { // as necessary BuildMessageProcessor proc = new BuildMessageProcessor(ctx); - // skip cfg(0) which is the gateway (us) - for (int i = 1; i < cfg.getLength(); i++) { + // skip cfg(0) which is the gateway (us) for outbound + // skip cfg(end) which is the endpoint (us) for inbound + int start = testType > 3 ? 0 : 1; + for (int i = start; i < end; i++) { // this not only decrypts the current hop's record, but encrypts the other records // with the reply key BuildRequestRecord req = proc.decrypt(msg, _peers[i], _privKeys[i]); @@ -132,9 +154,9 @@ public class BuildMessageTestStandalone extends TestCase { int ourSlot = -1; EncryptedBuildRecord reply; - if (testType == 1) { + if (testType == 1 || testType == 4) { reply = BuildResponseRecord.create(ctx, 0, req.readReplyKey(), req.readReplyIV(), i); - } else if (testType == 2) { + } else if (testType == 2 || testType == 5) { reply = BuildResponseRecord.create(ctx, 0, req.getChaChaReplyKey(), req.getChaChaReplyAD(), EmptyProperties.INSTANCE); } else { reply = BuildResponseRecord.createShort(ctx, 0, req.getChaChaReplyKey(), req.getChaChaReplyAD(), EmptyProperties.INSTANCE); @@ -156,14 +178,14 @@ public class BuildMessageTestStandalone extends TestCase { } } - if (testType == 1) { + if (testType == 1 || testType == 4) { log.debug("Read slot " + ourSlot + " containing hop " + i + " @ " + _peers[i].toBase64() + " receives on " + ourId + " sending to " + nextId + " replyKey " + Base64.encode(req.readReplyKey().getData()) + " replyIV " + Base64.encode(req.readReplyIV()) + " on " + nextPeer.toBase64() + " inGW? " + isInGW + " outEnd? " + isOutEnd + " time difference " + (now-time)); - } else if (testType == 2) { + } else if (testType == 2 || testType == 5) { log.debug("Read slot " + ourSlot + " containing hop " + i + " @ " + _peers[i].toBase64() + " receives on " + ourId + " sending to " + nextId + " replyKey " + Base64.encode(req.readReplyKey().getData()) @@ -218,7 +240,10 @@ public class BuildMessageTestStandalone extends TestCase { assertTrue(e.toString(), false); } } else { - reply = new TunnelBuildReplyMessage(ctx); + if (testType == 6) + reply = new ShortTunnelBuildReplyMessage(ctx, TunnelBuildMessage.MAX_RECORD_COUNT); + else + reply = new TunnelBuildReplyMessage(ctx); for (int i = 0; i < TunnelBuildMessage.MAX_RECORD_COUNT; i++) { reply.setRecord(i, msg.getRecord(i)); } @@ -257,15 +282,19 @@ public class BuildMessageTestStandalone extends TestCase { } private TunnelCreatorConfig createConfig(I2PAppContext ctx, int testType) { - return configOutbound(ctx, testType); + boolean isInbound = testType > 3; + if (isInbound) + testType -= 3; + return createConfig(ctx, testType, isInbound); } /** - * This creates a 3-hop (4 entries in the config) outbound tunnel. - * The first entry in the config is the gateway (us), + * This creates a 3-hop (4 entries in the config) tunnel. + * The first entry in the outbound config is the gateway (us), * and is mostly ignored. + * Ditto last entry in inbound config. */ - private TunnelCreatorConfig configOutbound(I2PAppContext ctx, int testType) { + private TunnelCreatorConfig createConfig(I2PAppContext ctx, int testType, boolean isInbound) { _peers = new Hash[4]; _pubKeys = new PublicKey[_peers.length]; _privKeys = new PrivateKey[_peers.length]; @@ -279,7 +308,7 @@ public class BuildMessageTestStandalone extends TestCase { _privKeys[i] = kp.getPrivate(); } - TunnelCreatorConfig cfg = new TCConfig(null, _peers.length, false); + TunnelCreatorConfig cfg = new TCConfig(null, _peers.length, isInbound); long now = ctx.clock().now(); // peers[] is ordered gateway first (unlike in production code) for (int i = 0; i < _peers.length; i++) { @@ -306,9 +335,9 @@ public class BuildMessageTestStandalone extends TestCase { RouterContext ctx = r.getContext(); ctx.initAll(); try { - test.x_testBuildMessage(ctx, 1); - test.x_testBuildMessage(ctx, 2); - test.x_testBuildMessage(ctx, 3); + for (int i = 1; i <= 6; i++) { + test.x_testBuildMessage(ctx, i); + } } finally { ctx.logManager().flush(); }