nquic_protocol_send (nquic v1.0.0)

View Source

Outbound 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

pre_encoded()

-type pre_encoded() :: {non_neg_integer(), iodata(), nquic_frame:t()}.

Functions

build_app_packet(Frames, State)

-spec build_app_packet([nquic_frame:t()], nquic_protocol:state()) ->
                          {ok, iodata(), nquic_protocol:state()} |
                          {error, term(), nquic_protocol:state()}.

build_app_packet_pre(PreEncoded, Time, State)

-spec build_app_packet_pre([pre_encoded()], integer(), nquic_protocol:state()) ->
                              {ok, iodata(), nquic_protocol:state()} |
                              {error, term(), nquic_protocol:state()}.

build_app_packet_pre_ctx(PreEncoded, Ctx, 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()}.

build_handshake_packet(Frames, 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.

build_initial_packet(Frames, State)

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

build_initial_packet(Frames, State, RetryToken)

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

build_packets_mtu_pre/5

-spec build_packets_mtu_pre([pre_encoded()],
                            pos_integer(),
                            integer(),
                            nquic_protocol:state(),
                            [iodata()]) ->
                               {[iodata()], nquic_protocol:state()}.

build_packets_mtu_pre_ctx/5

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

build_zero_rtt_packet_pre(PreEncoded, Time, State)

-spec build_zero_rtt_packet_pre([pre_encoded()], integer(), nquic_protocol:state()) ->
                                   {ok, iodata(), nquic_protocol:state()} |
                                   {error, term(), nquic_protocol:state()}.

check_anti_amplification/2

-spec check_anti_amplification(nquic_protocol:state(), non_neg_integer()) ->
                                  ok_no_track | ok_track | amplification_limited.

check_congestion_control/2

-spec check_congestion_control(nquic_protocol:state(), non_neg_integer()) ->
                                  ok | {blocked, non_neg_integer()}.

ensure_initial_keys/2

-spec ensure_initial_keys(nquic_packet:header(), nquic_protocol:state()) ->
                             {ok, nquic_protocol:state()} | {error, nquic_error:any_reason()}.

ensure_sample_size(Payload, PnLen)

-spec ensure_sample_size(iodata(), 1..4) -> iodata().

get_packet_len/2

-spec get_packet_len(nquic_packet:header(), non_neg_integer()) -> {ok, non_neg_integer()}.

handle_lost_frames/3

hp_sample/3

-spec hp_sample(binary(), binary(), non_neg_integer()) -> binary().

make_app_send_ctx(State, Time)

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

maybe_update_dcid/2

-spec maybe_update_dcid(nquic_packet:header(), nquic_protocol:state()) -> nquic_protocol:state().

outgoing_spin/1

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

packet_number_from_header/1

-spec packet_number_from_header(nquic_packet:header()) -> nquic_packet_number:t() | undefined.

packet_payload_budget/1

-spec packet_payload_budget(nquic_protocol:state()) -> pos_integer().

packet_space_from_header/1

-spec packet_space_from_header(nquic_packet:header()) -> nquic_packet:space().

take_frames_for_mtu_pre(Frames, Budget)

-spec take_frames_for_mtu_pre([pre_encoded()], pos_integer()) -> {[pre_encoded()], [pre_encoded()]}.