ping method

Future<bool> ping({
  1. Duration timeout = const Duration(seconds: 5),
})

Sends a PING under a fresh sequence and waits for its acknowledgement: true if the peer answered within timeout. Both peers acknowledge a numbered PING at once. The packet is tracked until acknowledged or the wait ends, so its ACK is matched (and gives an RTT sample); it is never retransmitted. A later packet's acknowledgement also answers, since it proves the same thing.

The PING used to be sent untracked, so on a connection with nothing else in flight its ACK matched nothing and this always timed out.

Implementation

Future<bool> ping({Duration timeout = const Duration(seconds: 5)}) async {
  if (_closing || closing) return false;

  final pingSequence = _packetManager.nextSequence;
  final packet = UDXPacket(
    destinationCid: cids.remoteCid,
    sourceCid: cids.localCid,
    destinationStreamId: 0, // Connection-level packet
    sourceStreamId: 0, // Connection-level packet
    sequence: pingSequence,
    frames: [PingFrame()],
  );

  final completer = Completer<bool>();
  Timer? timeoutTimer;
  StreamSubscription? ackSubscription;

  void finish(bool answered) {
    if (completer.isCompleted) return;
    timeoutTimer?.cancel();
    ackSubscription?.cancel();
    _packetManager.forget(pingSequence);
    completer.complete(answered);
  }

  ackSubscription = on('ack').listen((event) {
    final data = event.data;
    if (data is Map && data['largestAcked'] is int && (data['largestAcked'] as int) >= pingSequence) {
      finish(true);
    }
  });
  timeoutTimer = clock.timer(timeout, () => finish(false));

  _packetManager.trackUntilAcked(packet);
  try {
    send(packet.toBytes());
  } catch (e) {
    finish(false);
  }

  return completer.future;
}