write method
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;
}