diff --git a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
index eab07cbec5068d71b4904b8439bc334d9d352ea9..5fa4e4ca2429faa5b5592a84bdb00cce58f44da7 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
@@ -408,18 +408,29 @@ class EventPumper implements Runnable {
             }
         }
     }
-    
+
     /**
      *  Called by the connection when it has data ready to write.
      *  If we have bandwidth, calls con.Write() which calls wantsWrite(con).
      *  If no bandwidth, calls con.queuedWrite().
      */
     public void wantsWrite(NTCPConnection con, byte data[]) {
-        ByteBuffer buf = ByteBuffer.wrap(data);
-        FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestOutbound(data.length, 0, "NTCP write");//con, buf);
+        wantsWrite(con, data, 0, data.length);
+    }
+
+    /**
+     *  Called by the connection when it has data ready to write.
+     *  If we have bandwidth, calls con.Write() which calls wantsWrite(con).
+     *  If no bandwidth, calls con.queuedWrite().
+     *
+     *  @since 0.9.35 off/len version
+     */
+    public void wantsWrite(NTCPConnection con, byte data[], int off, int len) {
+        ByteBuffer buf = ByteBuffer.wrap(data, off, len);
+        FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestOutbound(len, 0, "NTCP write");//con, buf);
         if (req.getPendingRequested() > 0) {
             if (_log.shouldLog(Log.INFO))
-                _log.info("queued write on " + con + " for " + data.length);
+                _log.info("queued write on " + con + " for " + len);
             _context.statManager().addRateData("ntcp.wantsQueuedWrite", 1);
             con.queuedWrite(buf, req);
         } else {
diff --git a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java
index c303a4cd117aa1773bb85991d1239f65405327f0..11088e05c1c766abb43d804618aa03d5145fac12 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java
@@ -27,12 +27,6 @@ import net.i2p.util.SimpleByteCache;
  */
 class InboundEstablishState extends EstablishBase {
 
-    /**
-     * next index in _curEncrypted to write to (equals _curEncrypted length if the block is
-     * ready to decrypt)
-     */
-    private int _curEncryptedOffset;
-
     /** current encrypted block we are reading (IB only) or an IV buf used at the end for OB */
     private byte _curEncrypted[];
 
@@ -40,10 +34,12 @@ class InboundEstablishState extends EstablishBase {
     private RouterIdentity _aliceIdent;
 
     /** contains the decrypted aliceIndexSize + aliceIdent + tsA + padding + aliceSig */
-    private ByteArrayOutputStream _sz_aliceIdent_tsA_padding_aliceSig;
+    private final ByteArrayOutputStream _sz_aliceIdent_tsA_padding_aliceSig;
 
     /** how long we expect _sz_aliceIdent_tsA_padding_aliceSig to be when its full */
     private int _sz_aliceIdent_tsA_padding_aliceSigSize;
+
+    private static final int NTCP1_MSG1_SIZE = XY_SIZE + HXY_SIZE;
     
     public InboundEstablishState(RouterContext ctx, NTCPTransport transport, NTCPConnection con) {
         super(ctx, transport, con);
@@ -75,7 +71,16 @@ class InboundEstablishState extends EstablishBase {
      *  @return 1, 2, or 0 if unknown
      *  @since 0.9.35
      */
-    public int getVersion() { return 1; }
+    public int getVersion() {
+        if (!_transport.isNTCP2Enabled())
+            return 1;
+        synchronized (_stateLock) {
+            if (_state == State.IB_INIT)
+                return 0;
+            // TODO NTCP2 states
+            return 1;
+        } 
+    } 
 
     /**
      *  we are Bob, so receive these bytes as part of an inbound connection
@@ -90,19 +95,28 @@ class InboundEstablishState extends EstablishBase {
      *  is synchronized, should be OK. See isComplete()
      */
     private void receiveInbound(ByteBuffer src) {
-        while (_state == State.IB_INIT && src.hasRemaining()) {
-            byte c = src.get();
-            _X[_received++] = c;
-            if (_received >= XY_SIZE)
-                changeState(State.IB_GOT_X);
+        if (_state == State.IB_INIT && src.hasRemaining()) {
+            int remaining = src.remaining();
+            //if (remaining < NTCP1_MSG1_SIZE && _transport.isNTCP2Enabled()) {
+            //    // NTCP2
+            //}
+            int toGet = Math.min(remaining, XY_SIZE - _received);
+            src.get(_X, _received, toGet);
+            _received += toGet;
+            if (_received < XY_SIZE)
+                return;
+            changeState(State.IB_GOT_X);
+            _received = 0;
         }
-        while (_state == State.IB_GOT_X && src.hasRemaining()) {
-            int i = _received - XY_SIZE;
-            _received++;
-            byte c = src.get();
-            _hX_xor_bobIdentHash[i] = c;
-            if (i >= HXY_SIZE - 1)
-                changeState(State.IB_GOT_HX);
+
+        if (_state == State.IB_GOT_X && src.hasRemaining()) {
+            int toGet = Math.min(src.remaining(), HXY_SIZE - _received);
+            src.get(_hX_xor_bobIdentHash, _received, toGet);
+            _received += toGet;
+            if (_received < HXY_SIZE)
+                return;
+            changeState(State.IB_GOT_HX);
+            _received = 0;
         }
 
         if (_state == State.IB_GOT_HX) {
@@ -184,19 +198,28 @@ class InboundEstablishState extends EstablishBase {
                 _state == State.IB_GOT_RI) && src.hasRemaining()) {
 
                 // Collect a 16-byte block
-                while (_curEncryptedOffset < AES_SIZE && src.hasRemaining()) {
-                    _curEncrypted[_curEncryptedOffset++] = src.get();
-                    _received++;
+                if (_received < AES_SIZE && src.hasRemaining()) {
+                    int toGet = Math.min(src.remaining(), AES_SIZE - _received);
+                    src.get(_curEncrypted, _received, toGet);
+                    _received += toGet;
+                    if (_received < AES_SIZE) {
+                        // no more bytes available in the buffer, and only a partial
+                        // block was read, so we can't decrypt it.
+                        if (_log.shouldLog(Log.DEBUG))
+                            _log.debug(prefix() + "end of available data with only a partial block read (" +
+                                       + _received + ")");
+                        return;
+                    }
                 }
                 // Decrypt the 16-byte block
-                if (_curEncryptedOffset >= AES_SIZE) {
+                if (_received >= AES_SIZE) {
                     _context.aes().decrypt(_curEncrypted, 0, _curDecrypted, 0, _dh.getSessionKey(),
                                            _prevEncrypted, 0, AES_SIZE);
 
                     byte swap[] = _prevEncrypted;
                     _prevEncrypted = _curEncrypted;
                     _curEncrypted = swap;
-                    _curEncryptedOffset = 0;
+                    _received = 0;
 
                     if (_state == State.IB_SENT_Y) { // we are on the first decrypted block
                         int sz = (int)DataHelper.fromLong(_curDecrypted, 0, 2);
@@ -269,11 +292,6 @@ class InboundEstablishState extends EstablishBase {
                             return;
                     }
                 } else {
-                    // no more bytes available in the buffer, and only a partial
-                    // block was read, so we can't decrypt it.
-                    if (_log.shouldLog(Log.DEBUG))
-                        _log.debug(prefix() + "end of available data with only a partial block read (" +
-                                   _curEncryptedOffset + ", " + _received + ")");
                 }
         }
 
diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCP2Payload.java b/router/java/src/net/i2p/router/transport/ntcp/NTCP2Payload.java
index 6dc7759830dff3ae5943ec3eb0b7bc604e13575b..c197ac22fc158f7ced91e482060e9ef7d8381675 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/NTCP2Payload.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/NTCP2Payload.java
@@ -124,7 +124,7 @@ class NTCP2Payload {
      *  @param payload writes to it starting at off
      *  @return the new offset
      */
-    public int writePayload(byte[] payload, int off, List<Block> blocks) {
+    public static int writePayload(byte[] payload, int off, List<Block> blocks) {
         for (Block block : blocks) {
             off = block.write(payload, off);
         }
diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
index 2528fb64fd4b18ab1a07b46224a5b4bce068b34f..7c68b3d6e3108dcf956c26fc3d7cfae78dea269c 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
@@ -120,7 +120,7 @@ public class NTCPTransport extends TransportImpl {
     private boolean _enableNTCP2;
     private static final String NTCP2_PROTO_SHORT = "NXK2CS";
     private static final String OPT_NTCP2_SK = 'N' + NTCP2_PROTO_SHORT + "2s";
-    private static final int NTCP2_INT_VERSION = 2;
+    static final int NTCP2_INT_VERSION = 2;
     private static final String NTCP2_VERSION = Integer.toString(NTCP2_INT_VERSION);
     /** b64 static private key */
     private static final String PROP_NTCP2_SP = "i2np.ntcp2.sp";
@@ -1090,6 +1090,15 @@ public class NTCPTransport extends TransportImpl {
      */
     boolean isNTCP2Enabled() { return _enableNTCP2; }
 
+    /**
+     * The static priv key
+     *
+     * @since 0.9.35
+     */
+    byte[] getNTCP2StaticPrivkey() {
+        return _ntcp2StaticPrivkey;
+    }
+
     /**
      * Get the valid NTCP version of this NTCP address.
      *