diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessage.java b/router/java/src/net/i2p/data/i2np/I2NPMessage.java
index d1c7391402ef2aba19d096e47e0e253ee477898c..4580476bc42c5a8c661a3615a7db242e480e4817 100644
--- a/router/java/src/net/i2p/data/i2np/I2NPMessage.java
+++ b/router/java/src/net/i2p/data/i2np/I2NPMessage.java
@@ -127,15 +127,43 @@ public interface I2NPMessage extends DataStructure {
      * write the message to the buffer, returning the number of bytes written.
      * the data is formatted so as to be self contained, with the type, size,
      * expiration, unique id, as well as a checksum bundled along.  
-     * Full 16 byte header.
+     * Full 16 byte header for NTCP 1.
+     *
+     * @return the length written
      */
     public int toByteArray(byte buffer[]);
 
+    /** 
+     * write the message to the buffer, returning the number of bytes written.
+     * the data is formatted so as to be self contained, with the type, size,
+     * expiration, unique id, as well as a checksum bundled along.  
+     * Full 16 byte header for NTCP 1.
+     *
+     * @param off the offset to start writing at
+     * @return the new offset (NOT the length)
+     * @since 0.9.36
+     */
+    public int toByteArray(byte buffer[], int off);
+
     /**
      * write the message to the buffer, returning the number of bytes written.
      * the data is is not self contained - it does not include the size,
      * unique id, or any checksum, but does include the type and expiration.
-     * Short 5 byte header.
+     * Short 5 byte header for SSU.
+     *
+     * @return the length written
      */
     public int toRawByteArray(byte buffer[]);
+
+    /**
+     * write the message to the buffer, returning the number of bytes written.
+     * the data is is not self contained - it does not include the size,
+     * unique id, or any checksum, but does include the type and expiration.
+     * Short 9 byte header for NTCP 2.
+     *
+     * @param off the offset to start writing at
+     * @return the new offset (NOT the length)
+     * @since 0.9.36
+     */
+    public int toRawByteArrayNTCP2(byte buffer[], int off);
 }
diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java b/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
index 16506d1a1f6992fd0383cd4dc57ef453d6046f4f..69a7418c21e10e40fb737168dcd055e2ee81e5ae 100644
--- a/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
+++ b/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
@@ -53,9 +53,6 @@ public class I2NPMessageHandler {
             int type = (int)DataHelper.readLong(in, 1);
             _lastReadBegin = System.currentTimeMillis();
             I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type);
-            // can't be null
-            //if (msg == null)
-            //    throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message");
             try {
                 _lastSize = msg.readBytes(in, type, _messageBuffer);
             } catch (I2NPMessageException ime) {
@@ -114,19 +111,6 @@ public class I2NPMessageHandler {
         cur++;
         _lastReadBegin = System.currentTimeMillis();
         I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type);
-        // can't be null
-        //if (msg == null) {
-        //    int sz = data.length-offset;
-        //   boolean allZero = false;
-        //    for (int i = offset; i < data.length; i++) {
-        //        if (data[i] != 0) {
-        //            allZero = false;
-        //            break;
-        //        }
-        //    }
-        //    throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message (remaining sz=" 
-        //                                   + sz + " all zeros? " + allZero + ")");
-        //}
         try {
             _lastSize = msg.readBytes(data, type, cur, maxLen - 1);
             cur += _lastSize;
diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
index a9f29464ab6bafff94b11da6506fe62aa98809cf..78e20922f99474b537551054962b45c568a1aa6d 100644
--- a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
+++ b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
@@ -50,10 +50,6 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
                         + 2 // payload length
                         + CHECKSUM_LENGTH;
 
-    // Whether SSU used the full header or a truncated header.
-    // We are stuck with the short header, can't change it now.
-    //private static final boolean RAW_FULL_SIZE = false;
-
     /** unused */
     private static final Map<Integer, Builder> _builders = new ConcurrentHashMap<Integer, Builder>(1);
 
@@ -292,9 +288,6 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
      *  Used by SSU only!
      */
     public synchronized int getRawMessageSize() {
-        //if (RAW_FULL_SIZE)
-        //    return getMessageSize();
-        //else
             return calculateWrittenLength()+5;
     }
 
@@ -310,16 +303,38 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
         return data;
     }
 
+    /** 
+     * write the message to the buffer, returning the number of bytes written.
+     * the data is formatted so as to be self contained, with the type, size,
+     * expiration, unique id, as well as a checksum bundled along.  
+     * Full 16 byte header for NTCP 1.
+     *
+     * @return the length written
+     */
     public int toByteArray(byte buffer[]) {
+        return toByteArray(buffer, 0);
+    }
+
+    /** 
+     * write the message to the buffer, returning the number of bytes written.
+     * the data is formatted so as to be self contained, with the type, size,
+     * expiration, unique id, as well as a checksum bundled along.  
+     * Full 16 byte header for NTCP 1.
+     *
+     * @param off the offset to start writing at
+     * @return the new offset (NOT the length)
+     * @since 0.9.36 with off param
+     */
+    public int toByteArray(byte buffer[], int off) {
+        int start = off;
         try {
-            int writtenLen = writeMessageBody(buffer, HEADER_LENGTH);
-            int payloadLen = writtenLen - HEADER_LENGTH;
+            int rv = writeMessageBody(buffer, off + HEADER_LENGTH);
+            int payloadLen = rv - (off + HEADER_LENGTH);
             byte[] h = SimpleByteCache.acquire(Hash.HASH_LENGTH);
-            _context.sha().calculateHash(buffer, HEADER_LENGTH, payloadLen, h, 0);
+            _context.sha().calculateHash(buffer, off + HEADER_LENGTH, payloadLen, h, 0);
 
-            int off = 0;
             DataHelper.toLong(buffer, off, 1, getType());
-            off += 1;
+            off++;
 
             // Lazy initialization of value
             if (_uniqueId < 0) {
@@ -335,7 +350,7 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
             System.arraycopy(h, 0, buffer, off, CHECKSUM_LENGTH);
             SimpleByteCache.release(h);
 
-            return writtenLen;
+            return rv;
         } catch (I2NPMessageException ime) {
             _context.logManager().getLog(getClass()).log(Log.CRIT, "Error writing", ime);
             throw new IllegalStateException("Unable to serialize the message " + getClass().getSimpleName(), ime);
@@ -347,38 +362,18 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
 
     /**
      * write the message body to the output array, starting at the given index.
-     * @return the index into the array after the last byte written
+     * @return the index into the array after the last byte written (NOT the length)
      */
     protected abstract int writeMessageBody(byte out[], int curIndex) throws I2NPMessageException;
 
-    /*
-    protected int toByteArray(byte out[], byte[][] prefix, byte[][] suffix) throws I2NPMessageException {
-        int curIndex = 0;
-        for (int i = 0; i < prefix.length; i++) {
-            System.arraycopy(prefix[i], 0, out, curIndex, prefix[i].length);
-            curIndex += prefix[i].length;
-        }
-
-        curIndex = writeMessageBody(out, curIndex);
-
-        for (int i = 0; i < suffix.length; i++) {
-            System.arraycopy(suffix[i], 0, out, curIndex, suffix[i].length);
-            curIndex += suffix[i].length;
-        }
-
-        return curIndex;
-    }
-     */
-
-
     /**
      *  Write the message with a short 5-byte header.
      *  THe header consists of a one-byte type and a 4-byte expiration in seconds only.
      *  Used by SSU only!
+     *
+     *  @return the new written length
      */
     public int toRawByteArray(byte buffer[]) {
-        //if (RAW_FULL_SIZE)
-        //    return toByteArray(buffer);
         try {
             int off = 0;
             DataHelper.toLong(buffer, off, 1, getType());
@@ -394,6 +389,37 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
         }
     }
 
+    /**
+     * write the message to the buffer, returning the number of bytes written.
+     * the data is is not self contained - it does not include the size,
+     * unique id, or any checksum, but does include the type and expiration.
+     * Short 9 byte header for NTCP 2.
+     *
+     * @param off the offset to start writing at
+     * @return the new offset (NOT the length)
+     * @since 0.9.36
+     */
+    public int toRawByteArrayNTCP2(byte buffer[], int off) {
+        try {
+            DataHelper.toLong(buffer, off, 1, getType());
+            off += 1;
+            // Lazy initialization of value
+            if (_uniqueId < 0) {
+                _uniqueId = _context.random().nextLong(MAX_ID_VALUE);
+            }
+            DataHelper.toLong(buffer, off, 4, _uniqueId);
+            off += 4;
+            // January 19 2038? No, unsigned, good until Feb. 7 2106
+            // in seconds, round up so we don't lose time every hop
+            DataHelper.toLong(buffer, off, 4, (_expiration + 500) / 1000);
+            off += 4;
+            return writeMessageBody(buffer, off);
+        } catch (I2NPMessageException ime) {
+            _context.logManager().getLog(getClass()).log(Log.CRIT, "Error writing", ime);
+            throw new IllegalStateException("Unable to serialize the message " + getClass().getSimpleName(), ime);
+        }
+    }
+
     public void readMessage(byte data[], int offset, int dataSize, int type, I2NPMessageHandler handler) throws I2NPMessageException {
         // ignore the handler (overridden in subclasses if necessary
         try {
@@ -420,16 +446,6 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
         int type = buffer[offset] & 0xff;
         offset++;
         I2NPMessage msg = createMessage(ctx, type);
-        if (msg == null)
-            throw new I2NPMessageException("Unknown message type: " + type);
-        //if (RAW_FULL_SIZE) {
-        //    try {
-        //        msg.readBytes(buffer, type, offset);
-        //    } catch (IOException ioe) {
-        //        throw new I2NPMessageException("Error reading the " + msg, ioe);
-        //    }
-        //    return msg;
-        //}
 
         try {
             // January 19 2038? No, unsigned, good until Feb. 7 2106
@@ -456,8 +472,6 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
         int type = buffer[offset] & 0xff;
         offset++;
         I2NPMessage msg = createMessage(ctx, type);
-        if (msg == null)
-            throw new I2NPMessageException("Unknown message type: " + type);
 
         try {
             long id = DataHelper.fromLong(buffer, offset, 4);