AckFrame.fromReceived constructor

AckFrame.fromReceived(
  1. Iterable<int> sequences, {
  2. int ackDelay = 0,
})

Builds the ACK for a set of received sequences: the run ending at the largest, then (gap, run) pairs walking down.

It stops at a gap wider than maxGap or after maxRanges ranges, leaving the older ranges out. Leaving a range out only costs the sender a retransmission; encoding it truncated (a 290-packet gap written as 34) acknowledges sequences that never arrived, so the sender stops tracking them and never re-sends them.

Implementation

factory AckFrame.fromReceived(Iterable<int> sequences, {int ackDelay = 0}) {
  final sorted = sequences.toSet().toList()..sort();
  if (sorted.isEmpty) {
    throw ArgumentError('An ACK needs at least one received sequence');
  }
  // Contiguous runs, highest first.
  final runs = <List<int>>[]; // [start, end]
  var end = sorted.last;
  var start = end;
  for (var i = sorted.length - 2; i >= 0; i--) {
    if (sorted[i] == start - 1) {
      start = sorted[i];
    } else {
      runs.add([start, end]);
      end = sorted[i];
      start = end;
    }
  }
  runs.add([start, end]);

  final ranges = <AckRange>[];
  var prevStart = runs.first[0];
  for (final run in runs.skip(1)) {
    final gap = prevStart - run[1] - 1;
    if (gap > maxGap || ranges.length == maxRanges) break;
    ranges.add(AckRange(gap: gap, ackRangeLength: run[1] - run[0] + 1));
    prevStart = run[0];
  }
  return AckFrame(
    largestAcked: sorted.last,
    ackDelay: ackDelay,
    firstAckRangeLength: runs.first[1] - runs.first[0] + 1,
    ackRanges: ranges,
  );
}