write method

  1. @override
Future<void> write(
  1. Uint8List data
)
override

Writes data to the connection.

Implementation

@override
Future<void> write(Uint8List data) async {
  _log.finer('SecuredConnection.write: Plaintext data received (length: ${data.length}, first 20 bytes: ${data.take(20).toList()})');

  // libp2p Noise spec: 2-byte length prefix, max 65535 bytes per frame.
  // Max plaintext per frame = 65535 - 16 (MAC) = 65519 bytes.
  const maxPlaintextPerFrame = 65535 - 16;

  // Phase 1: Encrypt all chunks under the write lock (fast — microseconds).
  // The lock only covers nonce assignment + ChaCha20 encryption, NOT the
  // slow UDX transmission. This prevents yamux control frames (SYN-ACK,
  // WINDOW_UPDATE) from being blocked behind data frame UDX flow control.
  final encryptedChunks = <Uint8List>[];
  await _acquireWriteLock();
  try {
    var offset = 0;
    do {
      final chunkEnd = (offset + maxPlaintextPerFrame < data.length)
          ? offset + maxPlaintextPerFrame
          : data.length;
      final chunk = data.sublist(offset, chunkEnd);
      encryptedChunks.add(await _encryptFrame(chunk));
      offset = chunkEnd;
    } while (offset < data.length);
  } finally {
    _releaseWriteLock();
  }

  // Phase 2: Enqueue encrypted chunks for async transmission.
  // The write loop sends them in FIFO order (matching nonce order)
  // without holding any lock that would block other encryptions.
  final completer = Completer<void>();
  // Only the LAST chunk's pending write gets the caller's completer;
  // intermediate chunks get their own completers for error propagation.
  for (var i = 0; i < encryptedChunks.length; i++) {
    final isLast = i == encryptedChunks.length - 1;
    _pendingWrites.add(_PendingEncryptedWrite(
      encryptedChunks[i],
      isLast ? completer : Completer<void>(),
    ));
  }
  _startWriteLoop();

  // Wait for all chunks to be transmitted (backpressure to caller).
  await completer.future;
}