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();
         }