From 0a41052f3f5f30c69560d1fe59225b9ca00c7981 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Sun, 14 Sep 2014 14:32:23 +0000
Subject: [PATCH] SSU InboundMessageState - Rewrite PartialBitfield for
 efficiency, less space and object churn SSU ACKBitfield: Add ackCount()
 PeerState.fetchPartialACKs() improvements

---
 .../i2p/router/transport/udp/ACKBitfield.java | 11 ++++
 .../transport/udp/InboundMessageState.java    | 57 ++++++++++++-------
 .../i2p/router/transport/udp/PeerState.java   | 22 +++----
 .../router/transport/udp/UDPPacketReader.java | 20 +++++++
 4 files changed, 77 insertions(+), 33 deletions(-)

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 c8b7c96309..868c12cb5d 100644
--- a/router/java/src/net/i2p/router/transport/udp/ACKBitfield.java
+++ b/router/java/src/net/i2p/router/transport/udp/ACKBitfield.java
@@ -5,12 +5,23 @@ package net.i2p.router.transport.udp;
  * received messages
  */
 interface ACKBitfield {
+
     /** what message is this partially ACKing? */
     public long getMessageId(); 
+
     /** how many fragments are covered in this bitfield? */
     public int fragmentCount();
+
     /** has the given fragment been received? */
     public boolean received(int fragmentNum);
+
     /** has the entire message been received completely? */
     public boolean receivedComplete();
+
+    /**
+     *  Number of fragments acked in this bitfield.
+     *  Faster than looping through received()
+     *  @since 0.9.16
+     */
+    public int ackCount();
 }
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 4c11aa0f47..754eb6fff3 100644
--- a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java
+++ b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java
@@ -220,47 +220,62 @@ class InboundMessageState implements CDQEntry {
     }
 
     public ACKBitfield createACKBitfield() {
-        return new PartialBitfield(_messageId, _fragments);
+        int sz = (_lastFragment >= 0) ? _lastFragment + 1 : _fragments.length;
+        return new PartialBitfield(_messageId, _fragments, sz);
     }
     
     /**
-     *  A true partial bitfield that is not complete.
+     *  A true partial bitfield that is probably not complete.
+     *  fragmentCount() will return 64 if unknown.
      */
     private static final class PartialBitfield implements ACKBitfield {
         private final long _bitfieldMessageId;
-        private final boolean _fragmentsReceived[];
+        private final int _fragmentCount;
+        private final int _ackCount;
+        // bitfield, 1 for acked
+        private final long _fragmentAcks;
         
         /**
          *  @param data each element is non-null or null for received or not
+         *  @param lastFragment size of data to use
          */
-        public PartialBitfield(long messageId, Object data[]) {
+        public PartialBitfield(long messageId, Object data[], int size) {
+            if (size > MAX_FRAGMENTS)
+                throw new IllegalArgumentException();
             _bitfieldMessageId = messageId;
-            boolean fragmentsRcvd[] = null;
-            for (int i = data.length - 1; i >= 0; i--) {
+            int ackCount = 0;
+            long acks = 0;
+            for (int i = 0; i < size; i++) {
                 if (data[i] != null) {
-                    if (fragmentsRcvd == null)
-                        fragmentsRcvd = new boolean[i+1];
-                    fragmentsRcvd[i] = true;
+                    acks |= mask(i);
+                    ackCount++;
                 }
             }
-            if (fragmentsRcvd == null)
-                _fragmentsReceived = new boolean[0];
-            else
-                _fragmentsReceived = fragmentsRcvd;
+            _fragmentAcks = acks;
+            _fragmentCount = size;
+            _ackCount = ackCount;
         }
 
-        public int fragmentCount() { return _fragmentsReceived.length; }
+        /**
+         *  @param fragment 0-63
+         */
+        private static long mask(int fragment) {
+            return 1L << fragment;
+        }
+
+        public int fragmentCount() { return _fragmentCount; }
+
+        public int ackCount() { return _ackCount; }
 
         public long getMessageId() { return _bitfieldMessageId; }
 
         public boolean received(int fragmentNum) { 
-            if ( (fragmentNum < 0) || (fragmentNum >= _fragmentsReceived.length) )
+            if (fragmentNum < 0 || fragmentNum >= _fragmentCount)
                 return false;
-            return _fragmentsReceived[fragmentNum];
+            return (_fragmentAcks & mask(fragmentNum)) != 0;
         }
 
-        /** @return false always */
-        public boolean receivedComplete() { return false; }
+        public boolean receivedComplete() { return _ackCount == _fragmentCount; }
         
         @Override
         public String toString() { 
@@ -268,9 +283,11 @@ class InboundMessageState implements CDQEntry {
             buf.append("Partial ACK of ");
             buf.append(_bitfieldMessageId);
             buf.append(" with ACKs for: ");
-            for (int i = 0; i < _fragmentsReceived.length; i++)
-                if (_fragmentsReceived[i])
+            for (int i = 0; i < _fragmentCount; i++) {
+                if (received(i))
                     buf.append(i).append(" ");
+            }
+            buf.append(" / ").append(_fragmentCount);
             return buf.toString();
         }
     }
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 f456acc22e..b0c58e8791 100644
--- a/router/java/src/net/i2p/router/transport/udp/PeerState.java
+++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java
@@ -1035,7 +1035,7 @@ class PeerState {
      *            no full bitfields are included.
      */
     void fetchPartialACKs(List<ACKBitfield> rv) {
-        InboundMessageState states[] = null;
+        List<InboundMessageState> states = null;
         int curState = 0;
         synchronized (_inboundMessages) {
             int numMessages = _inboundMessages.size();
@@ -1052,17 +1052,17 @@ class PeerState {
                 } else {
                     if (!state.isComplete()) {
                         if (states == null)
-                            states = new InboundMessageState[numMessages];
-                        states[curState++] = state;
+                            states = new ArrayList<InboundMessageState>(numMessages);
+                        states.add(state);
                     }
                 }
             }
         }
         if (states != null) {
-            // _inboundMessages is a Map (unordered), so why bother going backwards?
-            for (int i = curState-1; i >= 0; i--) {
-                if (states[i] != null)
-                    rv.add(states[i].createACKBitfield());
+            for (InboundMessageState ims : states) {
+                ACKBitfield abf = ims.createACKBitfield();
+                if (!abf.receivedComplete())
+                    rv.add(abf);
             }
         }
     }
@@ -1076,6 +1076,7 @@ class PeerState {
         public FullACKBitfield(long id) { _msgId = id; }
 
         public int fragmentCount() { return 0; }
+        public int ackCount() { return 0; }
         public long getMessageId() { return _msgId; }
         public boolean received(int fragmentNum) { return true; }
         public boolean receivedComplete() { return true; }
@@ -1895,12 +1896,7 @@ class PeerState {
         if (state != null) {
             int numSends = state.getMaxSends();
                         
-            int bits = bitfield.fragmentCount();
-            int numACKed = 0;
-            for (int i = 0; i < bits; i++)
-                if (bitfield.received(i))
-                    numACKed++;
-            
+            int numACKed = bitfield.ackCount();
             _context.statManager().addRateData("udp.partialACKReceived", numACKed);
             
             if (_log.shouldLog(Log.INFO))
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 2b838eaab2..2ae0d7ad5f 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
@@ -529,6 +529,26 @@ class UDPPacketReader {
         public int fragmentCount() { return _bitfieldSize * 7; }
         public boolean receivedComplete() { return false; }
 
+        /**
+         *  Number of fragments acked in this bitfield.
+         *  Faster than looping through received()
+         *  @since 0.9.16
+         */
+        public int ackCount() {
+            int rv = 0;
+            for (int i = _bitfieldStart; i < _bitfieldStart + _bitfieldSize; i++) {
+                byte b = _message[i];
+                if ((b & 0x7f) != 0) {
+                    for (int j = 0; j < 7; j++) {
+                        if ((b & 0x01) != 0)
+                            rv++;
+                        b >>= 1;
+                    }
+                }
+            }
+            return rv;
+        }
+
         public boolean received(int fragmentNum) {
             if ( (fragmentNum < 0) || (fragmentNum >= _bitfieldSize*7) )
                 return false;
-- 
GitLab