diff --git a/router/java/src/net/i2p/router/transport/udp/ACKBitfield.java b/router/java/src/net/i2p/router/transport/udp/ACKBitfield.java
index 868c12cb5d73c29b6aeede4d734ea173742ffe03..b8c1a5f7e1e773ed96f76e5531a1858e30ed7db5 100644
--- a/router/java/src/net/i2p/router/transport/udp/ACKBitfield.java
+++ b/router/java/src/net/i2p/router/transport/udp/ACKBitfield.java
@@ -24,4 +24,11 @@ interface ACKBitfield {
      *  @since 0.9.16
      */
     public int ackCount();
+
+    /**
+     *  Highest fragment number acked in this bitfield.
+     *  @return highest fragment number acked, or -1 if none
+     *  @since 0.9.16
+     */
+    public int highestReceived();
 }
diff --git a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java
index 754eb6fff3a1e6711f019caf7956d7681b740c54..2e4f73f440b83434fc2d8e4686a5d4591512aa94 100644
--- a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java
+++ b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java
@@ -39,6 +39,9 @@ class InboundMessageState implements CDQEntry {
     private static final long MAX_RECEIVE_TIME = 10*1000;
     public static final int MAX_FRAGMENTS = 64;
     
+    /** 10 */
+    public static final int MAX_PARTIAL_BITFIELD_BYTES = (MAX_FRAGMENTS / 7) + 1;
+
     private static final int MAX_FRAGMENT_SIZE = UDPPacket.MAX_PACKET_SIZE;
     private static final ByteCache _fragmentCache = ByteCache.getInstance(64, MAX_FRAGMENT_SIZE);
     
@@ -232,6 +235,7 @@ class InboundMessageState implements CDQEntry {
         private final long _bitfieldMessageId;
         private final int _fragmentCount;
         private final int _ackCount;
+        private final int _highestReceived;
         // bitfield, 1 for acked
         private final long _fragmentAcks;
         
@@ -244,16 +248,19 @@ class InboundMessageState implements CDQEntry {
                 throw new IllegalArgumentException();
             _bitfieldMessageId = messageId;
             int ackCount = 0;
+            int highestReceived = -1;
             long acks = 0;
             for (int i = 0; i < size; i++) {
                 if (data[i] != null) {
                     acks |= mask(i);
                     ackCount++;
+                    highestReceived = i;
                 }
             }
             _fragmentAcks = acks;
             _fragmentCount = size;
             _ackCount = ackCount;
+            _highestReceived = highestReceived;
         }
 
         /**
@@ -267,6 +274,8 @@ class InboundMessageState implements CDQEntry {
 
         public int ackCount() { return _ackCount; }
 
+        public int highestReceived() { return _highestReceived; }
+
         public long getMessageId() { return _bitfieldMessageId; }
 
         public boolean received(int fragmentNum) { 
@@ -280,14 +289,15 @@ class InboundMessageState implements CDQEntry {
         @Override
         public String toString() { 
             StringBuilder buf = new StringBuilder(64);
-            buf.append("Partial ACK of ");
+            buf.append("OB Partial ACK of ");
             buf.append(_bitfieldMessageId);
-            buf.append(" with ACKs for: ");
+            buf.append(" highest: ").append(_highestReceived);
+            buf.append(" with ").append(_ackCount).append(" ACKs for: [");
             for (int i = 0; i < _fragmentCount; i++) {
                 if (received(i))
-                    buf.append(i).append(" ");
+                    buf.append(i).append(' ');
             }
-            buf.append(" / ").append(_fragmentCount);
+            buf.append("] / ").append(_fragmentCount);
             return buf.toString();
         }
     }
diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
index a68fe9dd5c51783ce65b9e879ffbc02734a99ad9..feb2b42403a091cdf05ba20532dbcb5c2a34b781 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
@@ -349,7 +349,15 @@ class PacketBuilder {
                     break;  // ack count
                 if (bf.receivedComplete())
                     continue;
-                int acksz = 4 + (bf.fragmentCount() / 7) + 1;
+                // only send what we have to
+                //int acksz = 4 + (bf.fragmentCount() / 7) + 1;
+                int bits = bf.highestReceived() + 1;
+                if (bits <= 0)
+                    continue;
+                int acksz = bits / 7;
+                if (bits % 7 > 0)
+                    acksz++;
+                acksz += 4;
                 if (partialAcksToSend == 0)
                     acksz++;  // ack count
                 if (availableForExplicitAcks >= acksz) {
@@ -414,10 +422,16 @@ class PacketBuilder {
             for (int i = 0; i < partialAcksToSend && iter.hasNext(); i++) {
                 ACKBitfield bitfield = iter.next();
                 if (bitfield.receivedComplete()) continue;
+                // only send what we have to
+                //int bits = bitfield.fragmentCount();
+                int bits = bitfield.highestReceived() + 1;
+                if (bits <= 0)
+                    continue;
+                int size = bits / 7;
+                if (bits % 7 > 0)
+                    size++;
                 DataHelper.toLong(data, off, 4, bitfield.getMessageId());
                 off += 4;
-                int bits = bitfield.fragmentCount();
-                int size = (bits / 7) + 1;
                 for (int curByte = 0; curByte < size; curByte++) {
                     if (curByte + 1 < size)
                         data[off] |= (byte)(1 << 7);
@@ -430,7 +444,7 @@ class PacketBuilder {
                 }
                 iter.remove();
                 if (msg != null) // logging it
-                    msg.append(' ').append(bitfield);
+                    msg.append(' ').append(bitfield).append(" with ack bytes: ").append(size);
             }
             //acksIncluded = true;
             // now jump back and fill in the number of bitfields *actually* included
@@ -607,11 +621,16 @@ class PacketBuilder {
             off++;
             for (int i = 0; i < ackBitfields.size(); i++) {
                 ACKBitfield bitfield = ackBitfields.get(i);
-                if (bitfield.receivedComplete()) continue;
+                // no, this will corrupt the packet
+                //if (bitfield.receivedComplete()) continue;
                 DataHelper.toLong(data, off, 4, bitfield.getMessageId());
                 off += 4;
-                int bits = bitfield.fragmentCount();
-                int size = (bits / 7) + 1;
+                // only send what we have to
+                //int bits = bitfield.fragmentCount();
+                int bits = bitfield.highestReceived() + 1;
+                int size = bits / 7;
+                if (bits == 0 || bits % 7 > 0)
+                    size++;
                 for (int curByte = 0; curByte < size; curByte++) {
                     if (curByte + 1 < size)
                         data[off] |= (byte)(1 << 7);
@@ -624,7 +643,7 @@ class PacketBuilder {
                 }
                 
                 if (msg != null) // logging it
-                    msg.append(" partial ack: ").append(bitfield);
+                    msg.append(" partial ack: ").append(bitfield).append(" with ack bytes: ").append(size);
             }
         }
         
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 b0c58e87913048bcee701059c6eb21afa9b58847..baf87e3b498a04f9c75b97709ad7597dd01389bd 100644
--- a/router/java/src/net/i2p/router/transport/udp/PeerState.java
+++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java
@@ -1075,8 +1075,9 @@ class PeerState {
 
         public FullACKBitfield(long id) { _msgId = id; }
 
-        public int fragmentCount() { return 0; }
-        public int ackCount() { return 0; }
+        public int fragmentCount() { return 1; }
+        public int ackCount() { return 1; }
+        public int highestReceived() { return 0; }
         public long getMessageId() { return _msgId; }
         public boolean received(int fragmentNum) { return true; }
         public boolean receivedComplete() { return true; }
@@ -1709,7 +1710,7 @@ class PeerState {
      */
     public int fragmentSize() {
         // 46 + 20 + 8 + 13 = 74 + 13 = 87 (IPv4)
-        // 46 + 40 + 8 + 13 = 74 + 13 = 107 (IPv6)
+        // 46 + 40 + 8 + 13 = 94 + 13 = 107 (IPv6)
         return _mtu -
                (_remoteIP.length == 4 ? PacketBuilder.MIN_DATA_PACKET_OVERHEAD : PacketBuilder.MIN_IPV6_DATA_PACKET_OVERHEAD) -
                MIN_ACK_SIZE;
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
index 2ae0d7ad5fa905d168d927752922fbfbeee6ab52..9aea7a101088875005148509dfd31de6c75f525e 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
@@ -519,8 +519,10 @@ class UDPPacketReader {
             int bfsz = 1;
             // bitfield is an array of bytes where the high bit is 1 if 
             // further bytes in the bitfield follow
-            while ((_message[_bitfieldStart + bfsz - 1] & UDPPacket.BITFIELD_CONTINUATION) != 0x0)
-                bfsz++;
+            while ((_message[_bitfieldStart + bfsz - 1] & UDPPacket.BITFIELD_CONTINUATION) != 0x0) {
+                if (++bfsz > InboundMessageState.MAX_PARTIAL_BITFIELD_BYTES)
+                    throw new IllegalArgumentException();
+            }
             _bitfieldSize = bfsz;
         }
 
@@ -549,6 +551,28 @@ class UDPPacketReader {
             return rv;
         }
 
+        /**
+         *  Highest fragment number acked in this bitfield.
+         *  @return highest fragment number acked, or -1 if none
+         *  @since 0.9.16
+         */
+        public int highestReceived() {
+            int count = fragmentCount();
+            int rv = -1;
+            for (int i = 0; i < _bitfieldSize; i++) {
+                byte b = _message[_bitfieldStart + i];
+                b &= 0x7f;
+                int j = 0;
+                while (b != 0 && j++ < 7) {
+                    if ((b & 0x01) != 0)
+                        rv = (7 * i) + j;
+                    b >>= 1;
+                    b &= 0x7f;
+                }
+            }
+            return rv;
+        }
+
         public boolean received(int fragmentNum) {
             if ( (fragmentNum < 0) || (fragmentNum >= _bitfieldSize*7) )
                 return false;
@@ -561,16 +585,17 @@ class UDPPacketReader {
         @Override
         public String toString() { 
             StringBuilder buf = new StringBuilder(64);
-            buf.append("Read partial ACK of ");
+            buf.append("IB Partial ACK of ");
             buf.append(getMessageId());
-            buf.append(" with ACKs for: ");
+            buf.append(" highest: ").append(highestReceived());
+            buf.append(" with ACKs for: [");
             int numFrags = fragmentCount();
             for (int i = 0; i < numFrags; i++) {
-                if (received(i))
-                    buf.append(i).append(" ");
-                else
-                    buf.append('!').append(i).append(" ");
+                if (!received(i))
+                    buf.append('!');
+                buf.append(i).append(' ');
             }
+            buf.append("] / ").append(numFrags);
             return buf.toString();
         }
     }