nquic_protocol_streams (nquic v1.0.0)
View SourceInbound STREAM frame processing and RESET_STREAM / STOP_SENDING handling.
Pure functions over #conn_state{} covering peer-initiated stream
ingest: STREAM frame processing with connection- and stream-level flow
control and window-update emission (handle_stream_frame/7,
maybe_send_max_data/1), DATA_BLOCKED / STREAM_DATA_BLOCKED responses
(respond_to_data_blocked/2, respond_to_stream_data_blocked/3), the
sender-side blocked signal (signal_blocked/4), and RESET_STREAM /
STOP_SENDING dispatch with final-size flow accounting
(handle_reset_stream/5, handle_reset_stream_new/2,
handle_stop_sending/3).
Send-side drain/blocked bookkeeping lives in
nquic_protocol_streams_send; terminal-state reclamation and
stream-limit window bookkeeping in nquic_protocol_streams_lifecycle.
Summary
Functions
Respond to a peer DATA_BLOCKED frame (RFC 9000 §4.1 / §19.12).
Unconditionally advertises a connection limit strictly above where
the peer reported it is blocked: max(local_max_data, PeerLimit + initial_max_data). This guarantees forward progress whenever a sender
signals it is stuck at the window edge; the receipt/reader ratchets
are proactive and can decline, but a peer that has explicitly said it
is blocked must always be granted headroom. Bounded and auto-tuning:
PeerLimit only grows as the peer makes progress, so the grant tracks
one window ahead of the peer's blocked offset rather than unbounded.
Respond to a peer STREAM_DATA_BLOCKED frame (RFC 9000 §19.13).
Per-stream analogue of respond_to_data_blocked/2: unconditionally
advertise max(recv_window, PeerLimit + initial_max_stream_data_bidi_local)
so a peer blocked on a stream limit is always granted headroom.
Unknown streams are ignored.
Mark a stream flow-control-blocked and signal the peer (RFC 9000 §4.1). A sender that is blocked by a flow-control limit SHOULD emit DATA_BLOCKED (connection) or STREAM_DATA_BLOCKED (stream) so the receiver can extend the limit. Emission is deduplicated per limit value so a stream parked across many retry turns produces one frame per limit, not one per turn.
Functions
-spec handle_reset_stream(nquic:stream_id(), non_neg_integer(), non_neg_integer(), #stream_state{stream_id :: nquic:stream_id(), type :: bidi | uni, send_state :: ready | send | data_sent | data_recvd | reset_sent | reset_recvd, send_offset :: non_neg_integer(), send_max_data :: non_neg_integer(), last_stream_data_blocked :: non_neg_integer(), pending_send_data :: [binary()], pending_send_size :: non_neg_integer(), pending_send_fin :: boolean(), recv_state :: recv | size_known | data_recvd | reset_recvd | data_read | reset_read, recv_offset :: non_neg_integer(), recv_max_offset :: non_neg_integer(), recv_window :: non_neg_integer(), recv_buffer :: gb_trees:tree(non_neg_integer(), {binary(), boolean()}), app_buffer :: iodata(), app_buffer_size :: non_neg_integer()}, nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
-spec handle_reset_stream_new(non_neg_integer(), nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
-spec handle_stop_sending(nquic:stream_id(), non_neg_integer(), nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()}.
-spec handle_stream_frame(nquic:stream_id(), non_neg_integer(), binary(), nquic_frame:t(), {ok, #stream_state{stream_id :: nquic:stream_id(), type :: bidi | uni, send_state :: ready | send | data_sent | data_recvd | reset_sent | reset_recvd, send_offset :: non_neg_integer(), send_max_data :: non_neg_integer(), last_stream_data_blocked :: non_neg_integer(), pending_send_data :: [binary()], pending_send_size :: non_neg_integer(), pending_send_fin :: boolean(), recv_state :: recv | size_known | data_recvd | reset_recvd | data_read | reset_read, recv_offset :: non_neg_integer(), recv_max_offset :: non_neg_integer(), recv_window :: non_neg_integer(), recv_buffer :: gb_trees:tree(non_neg_integer(), {binary(), boolean()}), app_buffer :: iodata(), app_buffer_size :: non_neg_integer()}} | error, map(), nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
-spec maybe_send_max_data(nquic_protocol:state()) -> nquic_protocol:state().
-spec respond_to_data_blocked(non_neg_integer(), nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()}.
Respond to a peer DATA_BLOCKED frame (RFC 9000 §4.1 / §19.12).
Unconditionally advertises a connection limit strictly above where
the peer reported it is blocked: max(local_max_data, PeerLimit + initial_max_data). This guarantees forward progress whenever a sender
signals it is stuck at the window edge; the receipt/reader ratchets
are proactive and can decline, but a peer that has explicitly said it
is blocked must always be granted headroom. Bounded and auto-tuning:
PeerLimit only grows as the peer makes progress, so the grant tracks
one window ahead of the peer's blocked offset rather than unbounded.
-spec respond_to_stream_data_blocked(nquic:stream_id(), non_neg_integer(), nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()}.
Respond to a peer STREAM_DATA_BLOCKED frame (RFC 9000 §19.13).
Per-stream analogue of respond_to_data_blocked/2: unconditionally
advertise max(recv_window, PeerLimit + initial_max_stream_data_bidi_local)
so a peer blocked on a stream limit is always granted headroom.
Unknown streams are ignored.
-spec signal_blocked(atom(), non_neg_integer(), nquic:stream_id(), nquic_protocol:state()) -> nquic_protocol:state().
Mark a stream flow-control-blocked and signal the peer (RFC 9000 §4.1). A sender that is blocked by a flow-control limit SHOULD emit DATA_BLOCKED (connection) or STREAM_DATA_BLOCKED (stream) so the receiver can extend the limit. Emission is deduplicated per limit value so a stream parked across many retry turns produces one frame per limit, not one per turn.