From b8407a261ec7810b8c65a7d1570a114babab1b98 Mon Sep 17 00:00:00 2001
From: zzz <zzz@i2pmail.org>
Date: Wed, 23 Feb 2022 15:55:03 -0500
Subject: [PATCH] SSU2: ACK bitfield support and conversion to ACK block

WIP, not hooked in
---
 .../router/transport/udp/SSU2Bitfield.java    | 335 ++++++++++++++++++
 1 file changed, 335 insertions(+)
 create mode 100644 router/java/src/net/i2p/router/transport/udp/SSU2Bitfield.java

diff --git a/router/java/src/net/i2p/router/transport/udp/SSU2Bitfield.java b/router/java/src/net/i2p/router/transport/udp/SSU2Bitfield.java
new file mode 100644
index 0000000000..62e2674a82
--- /dev/null
+++ b/router/java/src/net/i2p/router/transport/udp/SSU2Bitfield.java
@@ -0,0 +1,335 @@
+package net.i2p.router.transport.udp;
+
+import java.util.Arrays;
+
+import net.i2p.router.transport.udp.SSU2Payload.AckBlock;
+
+
+/**
+ * Container of a long array representing set and unset bits.
+ * When a bit higher than the current size + offset is set,
+ * the offset shifts up and the lowest set bits are lost.
+ *
+ * Also contains methods to convert to/from an ACK block.
+ *
+ * @since 0.9.54
+ */
+public class SSU2Bitfield {
+
+    private final long[] bitfield;
+    private final int size;
+    private final int max_shift;
+    private final int min_shift;
+    private long offset;
+    // before offset
+    private int highestSet = -1;
+
+    private static final long[] MASKS = new long[64];
+    static {
+        for (int i = 0; i < 64; i++) {
+            MASKS[i] = 1L << i;
+        }
+    }
+
+    /**
+     * Creates a new SSU2Bitfield that represents <code>size</code> unset bits.
+     */
+    public SSU2Bitfield(int size, long offset) {
+        if (size <= 0 || offset < 0)
+            throw new IllegalArgumentException();
+        // force mult. of 256
+        this.size = (size + 255) & 0x7FFFFF00;
+        this.offset = offset;
+        max_shift = Math.max(1024, size * 8);
+        min_shift = Math.max(8, size / 4);
+        bitfield = new long[size / 64];
+    }
+
+    public int size() {
+        return size;
+    }
+
+    public long getOffset() {
+        return offset;
+    }
+
+    /**
+     * Sets the given bit to true.
+     * When a bit higher than the current size + offset is set,
+     * the offset shifts up and the lowest set bits are lost.
+     *
+     * @throws IndexOutOfBoundsException if bit is smaller then zero
+     * @return previous value, true if previously set or unknown
+     */
+    public boolean set(long bit) {
+        if (bit < 0)
+            throw new IndexOutOfBoundsException(Long.toString(bit));
+        boolean rv;
+        synchronized(this) {
+            bit -= offset;
+            // too old?
+            if (bit < 0)
+                return true;
+            if (bit >= size) {
+                long shift = bit + 1 - size;
+                if (shift > max_shift)
+                    throw new IndexOutOfBoundsException("Shift too big: " + shift);
+                if (shift < min_shift)
+                    shift = min_shift;
+                // round up
+                if ((shift & 0x3f) != 0)
+                    shift = 64 + (shift & 0xc0);
+                //System.out.println("Shifting bitfield, offset was " + offset + ", now " + (offset + shift));
+                if (shift < size) {
+                    // shift down
+                    int bshift = (int) (shift / 64);
+                    System.arraycopy(bitfield, bshift, bitfield, 0, bitfield.length - bshift);
+                    // 2nd idx is exclusive
+                    Arrays.fill(bitfield, bitfield.length - bshift, bitfield.length, 0L);
+                    if (highestSet >= 0)
+                        highestSet -= shift;
+                } else {
+                    // start over
+                    Arrays.fill(bitfield, 0L);
+                    highestSet = -1;
+                }
+                offset += shift;
+                bit -= shift;
+            }
+            int index = (int) (bit >> 6);
+            long mask = MASKS[((int) bit) & 0x3F];
+            rv = (bitfield[index] & mask) != 0;
+            if (!rv) {
+                bitfield[index] |= mask;
+                if (bit > highestSet) {
+                    highestSet = (int) bit;
+                }
+            }
+        }
+        return rv;
+    }
+
+    /**
+     * Return true if the bit is set or false if it is not.
+     *
+     * @throws IndexOutOfBoundsException if bit is smaller then zero
+     */
+    public boolean get(long bit) {
+        if (bit < 0)
+            throw new IndexOutOfBoundsException(Long.toString(bit));
+        bit -= offset;
+        if (bit < 0 || bit >= size)
+            return false;
+        int index = (int) (bit >> 6);
+        long mask = MASKS[(int) (bit & 0x3F)];
+        return (bitfield[index] & mask) != 0;
+    }
+
+    /**
+     * Return the highest set bit, or -1 if none.
+     */
+    public synchronized long getHighestSet() {
+        if (highestSet < 0)
+            return -1;
+        return highestSet + offset;
+    }
+
+    /**
+     *  @param maxRanges may be 0
+     *  @return null if nothing is set
+     */
+    public synchronized AckBlock toAckBlock(int maxRanges) {
+        long highest = getHighestSet();
+        // nothing to ack
+        if (highest < 0)
+            return null;
+        //int lowest = getLowestUnset();
+        byte[] ranges = new byte[maxRanges * 2];
+        int acnt = 0;
+        // get acnt
+        int rangeCount = 0;
+
+        for (long i = highest - 1; i >= offset && acnt < 255; i--) {
+            if (!get(i))
+                break;
+            acnt++;
+        }
+        // now get ranges
+        if (acnt < highest - offset) {
+            // cur + 1 is set, cur is unset, start at cur
+            long cur = highest - (acnt + 1); 
+            for (int r = 0; r < maxRanges; r++) {
+                int ncnt = 0;
+                for ( ; cur >= offset && ncnt < 255; cur--) {
+                    if (get(cur)) {
+                        break;
+                    }
+                    ncnt++;
+                }
+                int aacnt = 0;
+                for ( ; cur >= offset && aacnt < 255; cur--) {
+                    if (!get(cur)) {
+                        break;
+                    }
+                    aacnt++;
+                }
+                if (ncnt == 0 && aacnt == 0)
+                    break;
+                ranges[rangeCount * 2] = (byte) ncnt;
+                ranges[(rangeCount * 2) + 1] = (byte) aacnt;
+                rangeCount++;
+                if (cur < offset)
+                    break;
+            }
+        }
+        //System.out.println(toString(highest, acnt, ranges, rangeCount));
+        return new AckBlock(highest, acnt, ranges, rangeCount);
+    }
+
+    /**
+     *  @param ranges may be null
+     */
+    public static SSU2Bitfield fromACKBlock(long thru, int acnt, byte[] ranges, int rangeCount) {
+        int t = (int) thru;
+        if (ranges == null || rangeCount == 0) {
+            // easy case, no ranges
+            SSU2Bitfield rv = new SSU2Bitfield(acnt + 1, t);
+            for (int i = t; i >= t - acnt; i--) {
+                rv.set(i);
+            }
+            return rv;
+        }
+        // get the minimum acked value
+        int min = t - acnt;
+        for (int i = 0; i < rangeCount * 2; i++) {
+            min -= ranges[i] & 0xff;
+        }
+        // fixup if the last ack count was zero
+        // this doesn't handle multple ranges with a zero ack count
+        if (ranges[(rangeCount * 2) - 1] == 0)
+            min += ranges[(rangeCount * 2) - 2] & 0xff;
+
+        SSU2Bitfield rv = new SSU2Bitfield(1 + t - min, min);
+        for (int i = t; i >= t - acnt; i--) {
+            rv.set(i);
+        }
+
+        int j = t - (acnt + 1);
+        for (int i = 0; i < rangeCount * 2; i += 2) {
+            // nack count
+            j -= ranges[i] & 0xff;
+            // ack count
+            int toAck = ranges[i + 1] & 0xff;
+            for (int k = 0; k < toAck; k++) {
+                rv.set(j--);
+            }
+        }
+        return rv;
+    }
+
+    /**
+     *  Pretty print an ACK block
+     *
+     *  @param ranges may be null
+     */
+    public static String toString(long thru, int acnt, byte[] ranges, int rangeCount) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("ACK ").append(thru);
+        int cur = (int) thru;
+        if (acnt > 0) {
+            sb.append('-').append(thru - acnt);
+            cur -= acnt;
+        }
+        if (ranges != null) {
+            cur--;
+            for (int i = 0; i < rangeCount * 2; i += 2) {
+                int nacks = ranges[i] & 0xff;
+                if (nacks > 0) {
+                    sb.append(" NACK ").append(cur);
+                    if (nacks > 1) {
+                        sb.append('-').append(cur - (nacks - 1));
+                    }
+                    cur -= nacks;
+                }
+                int acks = ranges[i+1] & 0xff;
+                if (acks > 0) {
+                    sb.append(" ACK ").append(cur);
+                    if (acks > 1) {
+                        sb.append('-').append(cur - (acks - 1));
+                    }
+                    cur -= acks;
+                }
+            }
+        }
+        sb.append("    RAW: ").append(thru).append(" A:").append(acnt);
+        if (ranges != null) {
+            for (int i = 0; i < rangeCount * 2; i += 2) {
+                sb.append(" N:").append(ranges[i] & 0xff);
+                sb.append(" A:").append(ranges[i + 1] & 0xff);
+            }
+         }
+         return sb.toString();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("SSU2Bitfield(");
+        sb.append(size).append(")");
+        sb.append(" offset: ").append(offset);
+        sb.append(" highest set: ").append(getHighestSet());
+        sb.append(" [");
+        for (long i = offset; i <= getHighestSet(); i++) {
+            if (get(i)) {
+              sb.append(' ');
+              sb.append(i);
+            }
+        }
+        sb.append(" ]");
+        return sb.toString();
+    }
+
+
+/****
+    public static void main(String[] args) {
+        int off = 100;
+        SSU2Bitfield bf = new SSU2Bitfield(256, off);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+        bf.set(off);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+        bf.set(off + 1);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+        bf.set(off + 2);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+        bf.set(off + 4);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+        bf.set(off + 5);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+
+        bf.set(off + 8);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+
+        bf.set(off + 88);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+
+        bf.set(off + 254);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+
+        bf.set(off + 255);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+
+        bf.set(off + 300);
+        System.out.println(bf.toString());
+        bf.toAckBlock(20);
+    }
+****/
+}
-- 
GitLab