drain method

Pull out every complete frame currently in the buffer. Partial frames stay buffered until the next feed.

Implementation

Iterable<RemoteFrame> drain() sync* {
  final pendingFatalError = _pendingFatalError;
  if (pendingFatalError != null) {
    _pendingFatalError = null;
    _clearBuffer();
    throw pendingFatalError;
  }
  while (true) {
    if (_bufferedLength < 5) return;
    final frameStart = _start;
    final type = FrameType.fromCode(_buffer[frameStart]);
    final length = ByteData.sublistView(
      _buffer,
      frameStart + 1,
      frameStart + 5,
    ).getUint32(0);
    final effectiveLimit = _effectivePayloadLimit(type);
    if (length > effectiveLimit) {
      // Clearing the buffer discards the current framing position with no
      // known next-frame boundary, so the stream can no longer be
      // resynchronized: mark this unrecoverable so a peer tears down and
      // reconnects instead of resyncing forever against a mirror that will
      // never advance (the "frozen, no banner" hang). A single frame whose
      // length was VALID but whose payload is malformed throws a recoverable
      // exception from _decode below (framing intact), so the two cases are
      // distinguishable at the catch site.
      _clearBuffer();
      throw _oversizedFrame(type, length, effectiveLimit);
    }
    final total = 5 + length;
    if (_bufferedLength < total) return;
    final before = _bufferedLength;
    final payloadStart = frameStart + 5;
    final payloadEnd = frameStart + total;
    _start = payloadEnd;
    if (type == null) {
      // The wire is lockstep, so an unknown discriminator is a protocol
      // violation, not a newer peer to tolerate. Framing has already
      // advanced past the frame, so the error is recoverable in the same
      // sense as a malformed known frame: it cannot recur on these bytes.
      final code = _buffer[frameStart];
      _releaseBufferIfEmpty();
      throw RemoteProtocolException(
        'unknown frame type 0x${code.toRadixString(16).padLeft(2, '0')}',
      );
    }
    // Copy exactly one completed payload. Partial feeds are retained in the
    // amortized byte buffer above instead of repeatedly materializing and
    // restoring the entire prefix (the old fragmented-frame O(n²) path).
    final payload = Uint8List.fromList(
      Uint8List.sublistView(_buffer, payloadStart, payloadEnd),
    );
    _releaseBufferIfEmpty();
    // Framing has advanced past this frame (the buffer now holds only the
    // trailing bytes), so a RECOVERABLE _decode throw below cannot recur on
    // these same bytes when the peer feeds again — the F19 non-loop
    // invariant a recoverable classification rests on.
    assert(
      _bufferedLength < before,
      'drain() must consume a frame from the buffer before decoding its '
      'payload, so a recoverable decode error cannot re-parse the same '
      'bytes forever (F19 non-loop invariant).',
    );
    yield _decode(type, payload);
  }
}