diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
index f3fccd13b4bd32b693902710f14cd0d479c7243d..93992cacdbc9327c4ccd118ec34b48f705f6c9eb 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder2.java
@@ -58,14 +58,14 @@ class PacketBuilder2 {
     static final int TYPE_CREAT = 73;
 
     /** IPv4 only */
-    public static final int IP_HEADER_SIZE = 20;
+    public static final int IP_HEADER_SIZE = PacketBuilder.IP_HEADER_SIZE;
     /** Same for IPv4 and IPv6 */
-    public static final int UDP_HEADER_SIZE = 8;
+    public static final int UDP_HEADER_SIZE = PacketBuilder.UDP_HEADER_SIZE;
 
     /** 74 */
     public static final int MIN_DATA_PACKET_OVERHEAD = IP_HEADER_SIZE + UDP_HEADER_SIZE + DATA_HEADER_SIZE + MAC_LEN;
 
-    public static final int IPV6_HEADER_SIZE = 40;
+    public static final int IPV6_HEADER_SIZE = PacketBuilder.IPV6_HEADER_SIZE;
     /** 94 */
     public static final int MIN_IPV6_DATA_PACKET_OVERHEAD = IPV6_HEADER_SIZE + UDP_HEADER_SIZE + DATA_HEADER_SIZE + MAC_LEN;
 
@@ -135,7 +135,7 @@ class PacketBuilder2 {
         // calculate data size
         int numFragments = fragments.size();
         int dataSize = 0;
-        int priority = 0;
+        int priority = PRIORITY_LOW;
         for (int i = 0; i < numFragments; i++) {
             Fragment frag = fragments.get(i);
             OutboundMessageState state = frag.state;
@@ -176,7 +176,7 @@ class PacketBuilder2 {
 
         // add the acks
         if (availableForAcks >= SSU2Payload.BLOCK_HEADER_SIZE + 5) {
-            int maxRanges = (availableForAcks - (SSU2Payload.BLOCK_HEADER_SIZE + 5)) / 2;
+            int maxRanges = Math.min((availableForAcks - (SSU2Payload.BLOCK_HEADER_SIZE + 5)) / 2, ABSOLUTE_MAX_ACK_RANGES);
             Block block = peer.getReceivedMessages().toAckBlock(maxRanges);
             if (block != null) {
                 blocks.add(block);
@@ -255,7 +255,7 @@ class PacketBuilder2 {
         pkt.setLength(off);
         encryptDataPacket(packet, peer.getSendCipher(), pktNum, peer.getSendHeaderEncryptKey1(), peer.getSendHeaderEncryptKey2());
         setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
-        packet.setPriority(0);
+        packet.setPriority(PRIORITY_LOW);
         return packet;
     }
 
diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java
index fd716c55f8836f6ec7dc127e49d6af76b43c9e43..23ba705327e245a0c562efa5b14686212cf7d396 100644
--- a/router/java/src/net/i2p/router/transport/udp/PeerState.java
+++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java
@@ -137,7 +137,7 @@ public class PeerState {
      */
     private volatile int _slowStartThreshold;
     /** what IP is the peer sending and receiving packets on? */
-    private final byte[] _remoteIP;
+    protected final byte[] _remoteIP;
     /** cached IP address */
     private volatile InetAddress _remoteIPAddress;
     /** what port is the peer sending and receiving packets on? */
@@ -160,7 +160,7 @@ public class PeerState {
      */
     private long _theyRelayToUsAs;
     /** what is the largest packet we can currently send to the peer? */
-    private int _mtu;
+    protected int _mtu;
     private int _mtuReceive;
     /** what is the largest packet we will ever send to the peer? */
     private int _largeMTU;
@@ -394,8 +394,6 @@ public class PeerState {
         _keyEstablishedTime = now;
         _lastSendTime = now;
         _lastReceiveTime = now;
-        _currentACKs = new ConcurrentHashSet<Long>();
-        _currentACKsResend = new LinkedBlockingQueue<ResendACK>();
         _slowStartThreshold = MAX_SEND_WINDOW_BYTES/2;
         _receivePeriodBegin = now;
         _remoteIP = addr.getAddress().getAddress();
@@ -428,11 +426,14 @@ public class PeerState {
         _inboundMessages = new HashMap<Long, InboundMessageState>(8);
         _outboundMessages = new CachedIteratorCollection<OutboundMessageState>();
         _outboundQueue = new PriBlockingQueue<OutboundMessageState>(ctx, "UDP-PeerState", 32);
-        _ackedMessages = new AckedMessages();
         _remotePeer = remotePeer;
         _isInbound = isInbound;
         _remoteHostId = new RemoteHostId(_remoteIP, _remotePort);
         _bwEstimator = new SimpleBandwidthEstimator(ctx, this);
+        // Unused in SSU2
+        _currentACKs = null;
+        _currentACKsResend = null;
+        _ackedMessages = null;
     }
     
     /**
diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState2.java b/router/java/src/net/i2p/router/transport/udp/PeerState2.java
index 8519a245093bb3ec5f9e5d34dbc056b2bd0d7c00..b05bf0de5420c38ba11ad4a117fdcfaaba1df5d8 100644
--- a/router/java/src/net/i2p/router/transport/udp/PeerState2.java
+++ b/router/java/src/net/i2p/router/transport/udp/PeerState2.java
@@ -3,6 +3,7 @@ package net.i2p.router.transport.udp;
 import java.net.DatagramPacket;
 import java.net.InetSocketAddress;
 import java.security.GeneralSecurityException;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import com.southernstorm.noise.protocol.CipherState;
@@ -66,8 +67,59 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
         _sentMessages = new SSU2Bitfield(256, 0);
     }
 
+    // SSU 1 overrides
+
     @Override
     public int getVersion() { return 2; }
+
+    /**
+     *  how much payload data can we shove in there?
+     *  Does NOT leave any room for acks, we'll fit them in when we can.
+     *  This is 5 bytes too low for first or only fragment.
+     *
+     *  @return MTU - 68 (IPv4), MTU - 88 (IPv6)
+     */
+    @Override
+    int fragmentSize() {
+        // 20 + 8 + 16 + 3 + 5 + 16 = 68 (IPv4)
+        // 40 + 8 + 16 + 3 + 5 + 16 = 88 (IPv6)
+        return _mtu -
+               (_remoteIP.length == 4 ? PacketBuilder2.MIN_DATA_PACKET_OVERHEAD : PacketBuilder2.MIN_IPV6_DATA_PACKET_OVERHEAD) -
+               DATA_FOLLOWON_EXTRA_SIZE; // Followon fragment block overhead (5)
+    }
+
+    /**
+     *  Packet overhead
+     *  Does NOT leave any room for acks, we'll fit them in when we can.
+     *  This is 5 bytes too high for first or only fragment.
+     *
+     *  @return 68 (IPv4), 88 (IPv6)
+     */
+    @Override
+    int fragmentOverhead() {
+        // 20 + 8 + 16 + 3 + 5 + 16 = 68 (IPv4)
+        // 40 + 8 + 16 + 3 + 5 + 16 = 88 (IPv6)
+        return (_remoteIP.length == 4 ? PacketBuilder2.MIN_DATA_PACKET_OVERHEAD : PacketBuilder2.MIN_IPV6_DATA_PACKET_OVERHEAD) +
+               DATA_FOLLOWON_EXTRA_SIZE; // Followon fragment block overhead (5)
+    }
+
+    // SSU 1 unsupported things
+
+    @Override
+    void setCurrentMACKey(SessionKey key) { throw new UnsupportedOperationException(); }
+    @Override
+    void setCurrentCipherKey(SessionKey key) { throw new UnsupportedOperationException(); }
+    @Override
+    List<Long> getCurrentFullACKs() { throw new UnsupportedOperationException(); }
+    @Override
+    List<Long> getCurrentResendACKs() { throw new UnsupportedOperationException(); }
+    @Override
+    void removeACKMessage(Long messageId) { throw new UnsupportedOperationException(); }
+    @Override
+    void fetchPartialACKs(List<ACKBitfield> rv) { throw new UnsupportedOperationException(); }
+
+    // SSU 2 things
+
     long getNextPacketNumber() { return _packetNumber.incrementAndGet(); }
     long getSendConnID() { return _sendConnID; }
     long getRcvConnID() { return _rcvConnID; }
diff --git a/router/java/src/net/i2p/router/transport/udp/SSU2Util.java b/router/java/src/net/i2p/router/transport/udp/SSU2Util.java
index aa4370815196845d1e90c60ce71cde1b00106365..8563ecc11b704e2a7b0b09e201eef7854d2346fa 100644
--- a/router/java/src/net/i2p/router/transport/udp/SSU2Util.java
+++ b/router/java/src/net/i2p/router/transport/udp/SSU2Util.java
@@ -55,17 +55,22 @@ final class SSU2Util {
     public static final int MIN_HANDSHAKE_DATA_LEN = SESSION_HEADER_SIZE + TOTAL_PROT_SAMPLE_LEN;
 
 
-    /** 3 byte block header + 9 byte I2NP header = 12 */
-    public static final int FULL_I2NP_HEADER_SIZE = SSU2Payload.BLOCK_HEADER_SIZE + 9;
+    /** 3 byte block header */
+    public static final int FULL_I2NP_HEADER_SIZE = SSU2Payload.BLOCK_HEADER_SIZE;
 
-    /** 3 byte block header + 9 byte I2NP header = 12 */
-    public static final int FIRST_FRAGMENT_HEADER_SIZE = SSU2Payload.BLOCK_HEADER_SIZE + 9;
+    /** 3 byte block header */
+    public static final int FIRST_FRAGMENT_HEADER_SIZE = SSU2Payload.BLOCK_HEADER_SIZE;
+
+    /**
+     * 5 for flag and msg number in followon block
+     */
+    public static final int DATA_FOLLOWON_EXTRA_SIZE = 5;
 
     /** 3 byte block header + 4 byte msg ID + 1 byte fragment info = 8 */
-    public static final int FOLLOWON_FRAGMENT_HEADER_SIZE = SSU2Payload.BLOCK_HEADER_SIZE + 5;
+    public static final int FOLLOWON_FRAGMENT_HEADER_SIZE = SSU2Payload.BLOCK_HEADER_SIZE + DATA_FOLLOWON_EXTRA_SIZE;
 
-    /** 16 byte block header + 2 + 12 = 30 */
-    public static final int DATA_HEADER_SIZE = SHORT_HEADER_SIZE + 2 + FULL_I2NP_HEADER_SIZE;
+    /** 16 byte short header + 3 = 19 */
+    public static final int DATA_HEADER_SIZE = SHORT_HEADER_SIZE + FULL_I2NP_HEADER_SIZE;
 
     /**
      *  The message types, 0-10, as bytes