From 3bec2b5c733ecc30e7b40001adfb2b90dfcc51ea Mon Sep 17 00:00:00 2001
From: zzz <zzz@i2pmail.org>
Date: Thu, 18 Feb 2021 10:30:51 -0500
Subject: [PATCH] I2NP: New build messages part 2 (prop. 157)

Rename ShortTunnelBuildReplyMessage to OutboundTunnelBuildReplyMessage (type 26)
Add InboundTunnelBuildMessage (type 27)
Add methods for plaintext record
Update readMessage() and writeMessageBody()
Fix calculateWrittenLength()
Update javadocs
WIP, untested, not hooked in yet
---
 .../data/i2np/InboundTunnelBuildMessage.java  | 127 ++++++++++++++++++
 .../i2np/OutboundTunnelBuildReplyMessage.java | 127 ++++++++++++++++++
 .../data/i2np/ShortTunnelBuildMessage.java    |   2 +-
 .../i2np/ShortTunnelBuildReplyMessage.java    |  76 -----------
 4 files changed, 255 insertions(+), 77 deletions(-)
 create mode 100644 router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java
 create mode 100644 router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java
 delete mode 100644 router/java/src/net/i2p/data/i2np/ShortTunnelBuildReplyMessage.java

diff --git a/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java b/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java
new file mode 100644
index 0000000000..77f9a6b945
--- /dev/null
+++ b/router/java/src/net/i2p/data/i2np/InboundTunnelBuildMessage.java
@@ -0,0 +1,127 @@
+package net.i2p.data.i2np;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
+
+/**
+ * Sent from the tunnel creator to the IBGW via an outbound tunnel.
+ * Contains one plaintext variable-sized request record for the IBGW
+ * and a variable number of encrypted records for the following hops.
+ * This message must be garlic-encrypted to hide the contents from the OBEP.
+ *
+ * Preliminary, see proposal 157.
+ *
+ * @since 0.9.50
+ */
+public class InboundTunnelBuildMessage extends TunnelBuildMessage {
+    public static final int MESSAGE_TYPE = 27;
+    public static final int SHORT_RECORD_SIZE = ShortTunnelBuildMessage.SHORT_RECORD_SIZE;
+    public static final int MAX_PLAINTEXT_RECORD_SIZE = OutboundTunnelBuildReplyMessage.MAX_PLAINTEXT_RECORD_SIZE;
+
+    private int _plaintextSlot;
+    private byte[] _plaintextRecord;
+
+    /** zero record count, will be set with readMessage() */
+    public InboundTunnelBuildMessage(I2PAppContext context) {
+        super(context, 0);
+    }
+
+    public InboundTunnelBuildMessage(I2PAppContext context, int records) {
+        super(context, records);
+    }
+
+    /**
+     *  Set the slot and data for the plaintext record.
+     *  @throws IllegalArgumentException on bad slot or data length.
+     */
+    public void setPlaintextRecord(int slot, byte[] data) {
+        if (slot < 0 || slot >= RECORD_COUNT || data.length == 0 || data.length > MAX_PLAINTEXT_RECORD_SIZE)
+            throw new IllegalArgumentException();
+        _plaintextSlot = slot;
+        _plaintextRecord = data;
+    }
+
+    /**
+     *  Get the slot for the plaintext record.
+     *  getRecord() for this slot will return null.
+     */
+    public int getPlaintextSlot() {
+        return _plaintextSlot;
+    }
+
+    /**
+     *  Get the data for the plaintext record.
+     */
+    public byte[] getPlaintextRecord() {
+        return _plaintextRecord;
+    }
+
+    @Override
+    protected int calculateWrittenLength() { return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE); }
+
+    @Override
+    public int getType() { return MESSAGE_TYPE; }
+
+    @Override
+    public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException {
+        if (type != MESSAGE_TYPE) 
+            throw new I2NPMessageException("Message type is incorrect for this message");
+        int r = data[offset++] & 0xff;
+        if (r <= 0 || r > MAX_RECORD_COUNT)
+            throw new I2NPMessageException("Bad record count " + r);
+        RECORD_COUNT = r;
+        int _plaintextSlot = data[offset++] & 0xff;
+        if (_plaintextSlot < 0 || _plaintextSlot >= r)
+            throw new I2NPMessageException("Bad slot " + _plaintextSlot);
+        int size = (int) DataHelper.fromLong(data, offset, 2);
+        if (size <= 0 || size > MAX_PLAINTEXT_RECORD_SIZE)
+            throw new I2NPMessageException("Bad size " + size);
+        offset += 2;
+        _plaintextRecord = new byte[size];
+        System.arraycopy(data, offset, _plaintextRecord, 0, size);
+        offset += size;
+
+        if (dataSize != calculateWrittenLength()) 
+            throw new I2NPMessageException("Wrong length (expects " + calculateWrittenLength() + ", recv " + dataSize + ")");
+        _records = new EncryptedBuildRecord[RECORD_COUNT];
+        for (int i = 0; i < RECORD_COUNT; i++) {
+            if (i == _plaintextSlot)
+                continue;
+            byte rec[] = new byte[SHORT_RECORD_SIZE];
+            System.arraycopy(data, offset, rec, 0, SHORT_RECORD_SIZE);
+            setRecord(i, new ShortEncryptedBuildRecord(rec));
+            offset += SHORT_RECORD_SIZE;
+        }
+    }
+    
+    @Override
+    protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException {
+        int remaining = out.length - (curIndex + calculateWrittenLength());
+        if (remaining < 0)
+            throw new I2NPMessageException("Not large enough (too short by " + remaining + ")");
+        if (RECORD_COUNT <= 0 || RECORD_COUNT > MAX_RECORD_COUNT)
+            throw new I2NPMessageException("Bad record count " + RECORD_COUNT);
+        out[curIndex++] = (byte) RECORD_COUNT;
+        out[curIndex++] = (byte) _plaintextSlot;
+        DataHelper.toLong(out, curIndex, 2, _plaintextRecord.length);
+        curIndex += 2;
+        System.arraycopy(_plaintextRecord, 0, out, curIndex, _plaintextRecord.length);
+        curIndex += _plaintextRecord.length;
+        for (int i = 0; i < RECORD_COUNT; i++) {
+            if (i == _plaintextSlot)
+                continue;
+            System.arraycopy(_records[i].getData(), 0, out, curIndex, SHORT_RECORD_SIZE);
+            curIndex += SHORT_RECORD_SIZE;
+        }
+        return curIndex;
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder(64);
+        buf.append("[InboundTunnelBuildMessage: " +
+                   "\n\tRecords: ").append(getRecordCount())
+           .append(']');
+        return buf.toString();
+    }
+}
diff --git a/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java b/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java
new file mode 100644
index 0000000000..ba8b6a513b
--- /dev/null
+++ b/router/java/src/net/i2p/data/i2np/OutboundTunnelBuildReplyMessage.java
@@ -0,0 +1,127 @@
+package net.i2p.data.i2np;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
+
+/**
+ * Sent from the OBEP to the tunnel creator via an inbound tunnel.
+ * Contains one plaintext variable-sized reply record for the creator
+ * and a variable number of encrypted records for the following hops.
+ * This message must be garlic-encrypted to hide the contents from the OBGW.
+ *
+ * Preliminary, see proposal 157.
+ *
+ * @since 0.9.50
+ */
+public class OutboundTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
+    public static final int MESSAGE_TYPE = 26;
+    public static final int SHORT_RECORD_SIZE = ShortTunnelBuildMessage.SHORT_RECORD_SIZE;
+    public static final int MAX_PLAINTEXT_RECORD_SIZE = 172;
+
+    private int _plaintextSlot;
+    private byte[] _plaintextRecord;
+
+    /** zero record count, will be set with readMessage() */
+    public OutboundTunnelBuildReplyMessage(I2PAppContext context) {
+        super(context, 0);
+    }
+
+    public OutboundTunnelBuildReplyMessage(I2PAppContext context, int records) {
+        super(context, records);
+    }
+
+    /**
+     *  Set the slot and data for the plaintext record.
+     *  @throws IllegalArgumentException on bad slot or data length.
+     */
+    public void setPlaintextRecord(int slot, byte[] data) {
+        if (slot < 0 || slot >= RECORD_COUNT || data.length == 0 || data.length > MAX_PLAINTEXT_RECORD_SIZE)
+            throw new IllegalArgumentException();
+        _plaintextSlot = slot;
+        _plaintextRecord = data;
+    }
+
+    /**
+     *  Get the slot for the plaintext record.
+     *  getRecord() for this slot will return null.
+     */
+    public int getPlaintextSlot() {
+        return _plaintextSlot;
+    }
+
+    /**
+     *  Get the data for the plaintext record.
+     */
+    public byte[] getPlaintextRecord() {
+        return _plaintextRecord;
+    }
+
+    @Override
+    protected int calculateWrittenLength() { return 4 + _plaintextRecord.length + ((RECORD_COUNT - 1) * SHORT_RECORD_SIZE); }
+
+    @Override
+    public int getType() { return MESSAGE_TYPE; }
+
+    @Override
+    public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException {
+        if (type != MESSAGE_TYPE) 
+            throw new I2NPMessageException("Message type is incorrect for this message");
+        int r = data[offset++] & 0xff;
+        if (r <= 0 || r > MAX_RECORD_COUNT)
+            throw new I2NPMessageException("Bad record count " + r);
+        RECORD_COUNT = r;
+        _plaintextSlot = data[offset++] & 0xff;
+        if (_plaintextSlot < 0 || _plaintextSlot >= r)
+            throw new I2NPMessageException("Bad slot " + _plaintextSlot);
+        int size = (int) DataHelper.fromLong(data, offset, 2);
+        if (size <= 0 || size > MAX_PLAINTEXT_RECORD_SIZE)
+            throw new I2NPMessageException("Bad size " + size);
+        offset += 2;
+        _plaintextRecord = new byte[size];
+        System.arraycopy(data, offset, _plaintextRecord, 0, size);
+        offset += size;
+
+        if (dataSize != calculateWrittenLength()) 
+            throw new I2NPMessageException("Wrong length (expects " + calculateWrittenLength() + ", recv " + dataSize + ")");
+        _records = new EncryptedBuildRecord[RECORD_COUNT];
+        for (int i = 0; i < RECORD_COUNT; i++) {
+            if (i == _plaintextSlot)
+                continue;
+            byte rec[] = new byte[SHORT_RECORD_SIZE];
+            System.arraycopy(data, offset, rec, 0, SHORT_RECORD_SIZE);
+            setRecord(i, new ShortEncryptedBuildRecord(rec));
+            offset += SHORT_RECORD_SIZE;
+        }
+    }
+    
+    @Override
+    protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException {
+        int remaining = out.length - (curIndex + calculateWrittenLength());
+        if (remaining < 0)
+            throw new I2NPMessageException("Not large enough (too short by " + remaining + ")");
+        if (RECORD_COUNT <= 0 || RECORD_COUNT > MAX_RECORD_COUNT)
+            throw new I2NPMessageException("Bad record count " + RECORD_COUNT);
+        out[curIndex++] = (byte) RECORD_COUNT;
+        out[curIndex++] = (byte) _plaintextSlot;
+        DataHelper.toLong(out, curIndex, 2, _plaintextRecord.length);
+        curIndex += 2;
+        System.arraycopy(_plaintextRecord, 0, out, curIndex, _plaintextRecord.length);
+        curIndex += _plaintextRecord.length;
+        for (int i = 0; i < RECORD_COUNT; i++) {
+            if (i == _plaintextSlot)
+                continue;
+            System.arraycopy(_records[i].getData(), 0, out, curIndex, SHORT_RECORD_SIZE);
+            curIndex += SHORT_RECORD_SIZE;
+        }
+        return curIndex;
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder(64);
+        buf.append("[OutboundTunnelBuildReplyMessage: " +
+                   "\n\tRecords: ").append(getRecordCount())
+           .append(']');
+        return buf.toString();
+    }
+}
diff --git a/router/java/src/net/i2p/data/i2np/ShortTunnelBuildMessage.java b/router/java/src/net/i2p/data/i2np/ShortTunnelBuildMessage.java
index a8591589d8..36821607f4 100644
--- a/router/java/src/net/i2p/data/i2np/ShortTunnelBuildMessage.java
+++ b/router/java/src/net/i2p/data/i2np/ShortTunnelBuildMessage.java
@@ -22,7 +22,7 @@ public class ShortTunnelBuildMessage extends TunnelBuildMessage {
     }
 
     @Override
-    protected int calculateWrittenLength() { return 1 + super.calculateWrittenLength(); }
+    protected int calculateWrittenLength() { return 1 + (RECORD_COUNT * SHORT_RECORD_SIZE); }
 
     @Override
     public int getType() { return MESSAGE_TYPE; }
diff --git a/router/java/src/net/i2p/data/i2np/ShortTunnelBuildReplyMessage.java b/router/java/src/net/i2p/data/i2np/ShortTunnelBuildReplyMessage.java
deleted file mode 100644
index fab4bf0677..0000000000
--- a/router/java/src/net/i2p/data/i2np/ShortTunnelBuildReplyMessage.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package net.i2p.data.i2np;
-
-import net.i2p.I2PAppContext;
-import net.i2p.data.DataHelper;
-
-/**
- * Transmitted from the new outbound endpoint to the creator through a
- * reply tunnel.
- * Variable size, small records.
- * Preliminary, see proposal 157.
- *
- * @since 0.9.49
- */
-public class ShortTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
-    public static final int MESSAGE_TYPE = 26;
-    public static final int SHORT_RECORD_SIZE = ShortTunnelBuildMessage.SHORT_RECORD_SIZE;
-
-    /** zero record count, will be set with readMessage() */
-    public ShortTunnelBuildReplyMessage(I2PAppContext context) {
-        super(context, 0);
-    }
-
-    public ShortTunnelBuildReplyMessage(I2PAppContext context, int records) {
-        super(context, records);
-    }
-
-    @Override
-    protected int calculateWrittenLength() { return 1 + super.calculateWrittenLength(); }
-
-    @Override
-    public int getType() { return MESSAGE_TYPE; }
-
-    @Override
-    public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException {
-        if (type != MESSAGE_TYPE) 
-            throw new I2NPMessageException("Message type is incorrect for this message");
-        int r = data[offset] & 0xff;
-        if (r <= 0 || r > MAX_RECORD_COUNT)
-            throw new I2NPMessageException("Bad record count " + r);
-        RECORD_COUNT = r;
-        if (dataSize != calculateWrittenLength()) 
-            throw new I2NPMessageException("Wrong length (expects " + calculateWrittenLength() + ", recv " + dataSize + ")");
-        _records = new EncryptedBuildRecord[RECORD_COUNT];
-        offset++;
-        for (int i = 0; i < RECORD_COUNT; i++) {
-            byte rec[] = new byte[SHORT_RECORD_SIZE];
-            System.arraycopy(data, offset, rec, 0, SHORT_RECORD_SIZE);
-            setRecord(i, new ShortEncryptedBuildRecord(rec));
-            offset += SHORT_RECORD_SIZE;
-        }
-    }
-    
-    @Override
-    protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException {
-        int remaining = out.length - (curIndex + calculateWrittenLength());
-        if (remaining < 0)
-            throw new I2NPMessageException("Not large enough (too short by " + remaining + ")");
-        if (RECORD_COUNT <= 0 || RECORD_COUNT > MAX_RECORD_COUNT)
-            throw new I2NPMessageException("Bad record count " + RECORD_COUNT);
-        out[curIndex++] = (byte) RECORD_COUNT;
-        for (int i = 0; i < RECORD_COUNT; i++) {
-            System.arraycopy(_records[i].getData(), 0, out, curIndex, SHORT_RECORD_SIZE);
-            curIndex += SHORT_RECORD_SIZE;
-        }
-        return curIndex;
-    }
-    
-    @Override
-    public String toString() {
-        StringBuilder buf = new StringBuilder(64);
-        buf.append("[ShortTunnelBuildReplyMessage: " +
-                   "\n\tRecords: ").append(getRecordCount())
-           .append(']');
-        return buf.toString();
-    }
-}
-- 
GitLab