nquic_protocol_send (nquic v1.0.0)
View SourceOutbound side of the QUIC protocol state.
Pure functions over #conn_state{} covering encrypted packet
construction (Initial / Handshake / 1-RTT / 0-RTT), MTU-aware
splitting and batched send, the per-flush send context,
anti-amplification gating (RFC 9000 §8.1), congestion-control
admission (RFC 9002), header protection sample extraction
(RFC 9001 §5.4), Initial-key derivation, and the loss-detection
retransmission glue.
ACK generation lives in nquic_protocol_ack; the per-encryption-level
pending-frame queues and their flush drains live in
nquic_protocol_send_queues. Both call down into this module's packet
builders module-qualified; the dependency is one-way.
Summary
Functions
Build an encrypted Handshake-space packet from a list of frames.
Same return shape as build_initial_packet/2. Returns
{error, no_handshake_keys, nquic_protocol:state()} before Handshake keys are
installed.
Build an encrypted Initial-space packet from a list of frames.
Returns {ok, iodata(), nquic_protocol:state()} with the masked packet and the state
updated for loss detection, anti-amplification, and packet-number space.
The wire datagram is padded to RFC 9000 §14.1's 1200-byte minimum.
Returns {ok, <<>>, nquic_protocol:state()} if the connection is anti-amplification
limited (server has not yet validated the client). The caller should
not send anything in that case.
Returns {error, no_initial_keys, nquic_protocol:state()} if Initial keys have not
been derived yet (caller bug).
Used by flush/1 to drain pending_initial_frames and by callers that
need to send a single Initial packet directly (e.g. client first flight
once the handshake API exposes it).
Build an encrypted Initial-space packet with a Retry token.
Same shape as build_initial_packet/2. The token is included in the
long-header packet (see RFC 9000 §17.2.5, Retry); pass <<>> for
no-token Initials.
Outgoing latency spin bit for 1-RTT packets (RFC 9000 §17.4). Returns 0 when the spin bit is disabled or no peer packet has been seen yet. When enabled, the server inverts the peer's last spin sample and the client mirrors it.
Types
-type pre_encoded() :: {non_neg_integer(), iodata(), nquic_frame:t()}.
Functions
-spec build_app_packet([nquic_frame:t()], nquic_protocol:state()) -> {ok, iodata(), nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
-spec build_app_packet_pre([pre_encoded()], integer(), nquic_protocol:state()) -> {ok, iodata(), nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
-spec build_app_packet_pre_ctx([pre_encoded()], #app_send_ctx{cipher :: aes_128_gcm | aes_256_gcm | chacha20_poly1305, role_keys :: map(), key :: binary(), iv :: binary(), hp_source :: {ctx, crypto:crypto_state()} | {key, binary()}, dcid :: nquic:connection_id(), dcid_prefix_size :: pos_integer(), gso_size :: undefined | pos_integer(), pad_bin :: undefined | binary(), key_phase :: boolean(), largest_acked :: non_neg_integer(), time :: integer(), track_anti_amp :: boolean()}, nquic_protocol:state()) -> {ok, iodata(), nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
-spec build_handshake_packet([nquic_frame:t()], nquic_protocol:state()) -> {ok, iodata(), nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
Build an encrypted Handshake-space packet from a list of frames.
Same return shape as build_initial_packet/2. Returns
{error, no_handshake_keys, nquic_protocol:state()} before Handshake keys are
installed.
-spec build_initial_packet([nquic_frame:t()], nquic_protocol:state()) -> {ok, iodata(), nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
Build an encrypted Initial-space packet from a list of frames.
Returns {ok, iodata(), nquic_protocol:state()} with the masked packet and the state
updated for loss detection, anti-amplification, and packet-number space.
The wire datagram is padded to RFC 9000 §14.1's 1200-byte minimum.
Returns {ok, <<>>, nquic_protocol:state()} if the connection is anti-amplification
limited (server has not yet validated the client). The caller should
not send anything in that case.
Returns {error, no_initial_keys, nquic_protocol:state()} if Initial keys have not
been derived yet (caller bug).
Used by flush/1 to drain pending_initial_frames and by callers that
need to send a single Initial packet directly (e.g. client first flight
once the handshake API exposes it).
-spec build_initial_packet([nquic_frame:t()], nquic_protocol:state(), binary()) -> {ok, iodata(), nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
Build an encrypted Initial-space packet with a Retry token.
Same shape as build_initial_packet/2. The token is included in the
long-header packet (see RFC 9000 §17.2.5, Retry); pass <<>> for
no-token Initials.
-spec build_packets_mtu_pre([pre_encoded()], pos_integer(), integer(), nquic_protocol:state(), [iodata()]) -> {[iodata()], nquic_protocol:state()}.
-spec build_packets_mtu_pre_ctx([pre_encoded()], pos_integer(), #app_send_ctx{cipher :: aes_128_gcm | aes_256_gcm | chacha20_poly1305, role_keys :: map(), key :: binary(), iv :: binary(), hp_source :: {ctx, crypto:crypto_state()} | {key, binary()}, dcid :: nquic:connection_id(), dcid_prefix_size :: pos_integer(), gso_size :: undefined | pos_integer(), pad_bin :: undefined | binary(), key_phase :: boolean(), largest_acked :: non_neg_integer(), time :: integer(), track_anti_amp :: boolean()}, nquic_protocol:state(), [iodata()]) -> {[iodata()], nquic_protocol:state()}.
-spec build_zero_rtt_packet_pre([pre_encoded()], integer(), nquic_protocol:state()) -> {ok, iodata(), nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
-spec check_anti_amplification(nquic_protocol:state(), non_neg_integer()) -> ok_no_track | ok_track | amplification_limited.
-spec check_congestion_control(nquic_protocol:state(), non_neg_integer()) -> ok | {blocked, non_neg_integer()}.
-spec ensure_initial_keys(nquic_packet:header(), nquic_protocol:state()) -> {ok, nquic_protocol:state()} | {error, nquic_error:any_reason()}.
-spec get_packet_len(nquic_packet:header(), non_neg_integer()) -> {ok, non_neg_integer()}.
-spec handle_lost_frames([nquic_frame:t()], nquic_packet:space(), nquic_protocol:state()) -> nquic_protocol:state().
-spec hp_sample(binary(), binary(), non_neg_integer()) -> binary().
-spec make_app_send_ctx(nquic_protocol:state(), integer()) -> #app_send_ctx{cipher :: aes_128_gcm | aes_256_gcm | chacha20_poly1305, role_keys :: map(), key :: binary(), iv :: binary(), hp_source :: {ctx, crypto:crypto_state()} | {key, binary()}, dcid :: nquic:connection_id(), dcid_prefix_size :: pos_integer(), gso_size :: undefined | pos_integer(), pad_bin :: undefined | binary(), key_phase :: boolean(), largest_acked :: non_neg_integer(), time :: integer(), track_anti_amp :: boolean()}.
-spec maybe_update_dcid(nquic_packet:header(), nquic_protocol:state()) -> nquic_protocol:state().
-spec outgoing_spin(nquic_protocol:state()) -> 0..1.
Outgoing latency spin bit for 1-RTT packets (RFC 9000 §17.4). Returns 0 when the spin bit is disabled or no peer packet has been seen yet. When enabled, the server inverts the peer's last spin sample and the client mirrors it.
-spec packet_number_from_header(nquic_packet:header()) -> nquic_packet_number:t() | undefined.
-spec packet_payload_budget(nquic_protocol:state()) -> pos_integer().
-spec packet_space_from_header(nquic_packet:header()) -> nquic_packet:space().
-spec take_frames_for_mtu_pre([pre_encoded()], pos_integer()) -> {[pre_encoded()], [pre_encoded()]}.