nquic_protocol_streams (nquic v1.0.0)

View Source

Inbound 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

handle_reset_stream(StreamID, FinalSize, AppErrorCode, Stream, State)

-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()}.

handle_reset_stream_new(FinalSize, State)

-spec handle_reset_stream_new(non_neg_integer(), nquic_protocol:state()) ->
                                 {ok, [nquic_protocol:event()], nquic_protocol:state()} |
                                 {error, term(), nquic_protocol:state()}.

handle_stop_sending(StreamID, ErrCode, State)

handle_stream_frame(StreamID, Offset, StreamData, Frame, Existing, Limits, 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()}.

maybe_send_max_data(State)

-spec maybe_send_max_data(nquic_protocol:state()) -> nquic_protocol:state().

respond_to_data_blocked(PeerLimit, 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.

respond_to_stream_data_blocked(StreamID, PeerLimit, State)

-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.

signal_blocked/4

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.