nquic_protocol_recv (nquic v1.0.0)
View SourceInbound side of the QUIC protocol state.
Pure functions over #conn_state{} covering datagram parsing
(process_datagram/3), per-packet dispatch (handle_single_packet/4,
decrypt_and_process/4), Version Negotiation and Retry handling
(RFC 9000 §6, §17.2.5), stateless-reset detection (RFC 9000 §10.3.1),
the per-frame handler dispatch table (handle_frame/3 for every
RFC 9000 / 9221 frame type), and CRYPTO fragment reassembly
(RFC 9001 §4.1.3).
Extracted from nquic_protocol as part of REVIEW_PLAN.md Phase 4.4.
The trunk's public dispatchers (handle_packet/3,4,
handle_packet_notimers/3,4) call into process_datagram/3 here;
stream-frame and stream-cleanup helpers live in nquic_protocol
until slice 7 moves them out.
Summary
Functions
Handle a Retry packet (RFC 9000 §17.2.5).
A client that has not yet processed a Retry verifies the integrity
tag, adopts the server's SCID as the new DCID, stashes the original
DCID as odcid and the Retry token on #conn_state.retry_token,
re-derives Initial-space keys against the new DCID, resets the
Initial PN space and loss detector, and queues a fresh ClientHello
via queue_initial_frame/2. Subsequent Initial packets (queued
retransmits, PTO probes) carry the token through
build_initial_packet/2.
A Retry that fails any check (server role, second Retry, parse
error, integrity-tag mismatch) is silently dropped per RFC 9000
§17.2.5.1.
Handle a Version Negotiation packet. RFC 9000 §6 governs acceptance
Types
-type crypto_buffer_entry() :: {non_neg_integer(), iodata(), [{non_neg_integer(), binary()}]}.
Functions
-spec check_stateless_reset(binary(), nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()}.
-spec crypto_buffer_add(non_neg_integer(), binary(), crypto_buffer_entry()) -> crypto_buffer_entry().
-spec crypto_buffer_data(crypto_buffer_entry()) -> binary().
-spec crypto_buffer_merge(non_neg_integer(), iodata(), [{non_neg_integer(), binary()}]) -> crypto_buffer_entry().
-spec handle_frame(nquic_frame:t(), nquic_packet:header(), nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
-spec handle_frames([nquic_frame:t()], nquic_packet:header(), nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
-spec handle_retry_packet(binary(), #long_header{type :: initial | handshake | rtt0 | retry | version_negotiation, version :: 0..4294967295, dcid :: nquic:connection_id(), scid :: nquic:connection_id(), token :: binary() | undefined, payload_len :: non_neg_integer() | undefined, packet_number :: nquic_packet_number:t() | undefined, pn_len :: 1..4 | undefined}, nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()}.
Handle a Retry packet (RFC 9000 §17.2.5).
A client that has not yet processed a Retry verifies the integrity
tag, adopts the server's SCID as the new DCID, stashes the original
DCID as odcid and the Retry token on #conn_state.retry_token,
re-derives Initial-space keys against the new DCID, resets the
Initial PN space and loss detector, and queues a fresh ClientHello
via queue_initial_frame/2. Subsequent Initial packets (queued
retransmits, PTO probes) carry the token through
build_initial_packet/2.
A Retry that fails any check (server role, second Retry, parse
error, integrity-tag mismatch) is silently dropped per RFC 9000
§17.2.5.1.
-spec handle_single_packet(binary(), binary(), nquic_packet:header(), nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
-spec handle_version_negotiation(#long_header{type :: initial | handshake | rtt0 | retry | version_negotiation, version :: 0..4294967295, dcid :: nquic:connection_id(), scid :: nquic:connection_id(), token :: binary() | undefined, payload_len :: non_neg_integer() | undefined, packet_number :: nquic_packet_number:t() | undefined, pn_len :: 1..4 | undefined}, nquic_protocol:state()) -> {ok, [nquic_protocol:event()], nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.
Handle a Version Negotiation packet. RFC 9000 §6 governs acceptance:
- a client MUST discard VN packets that arrive after any other
server packet has been successfully decrypted (the
server_packet_processedflag latches once that happens), - VN must echo the client's chosen connection IDs (DCID = our SCID, SCID = the DCID we used on the Initial that triggered the VN); mismatched echoes look like injected VN and are dropped,
- a VN that lists the version the client was attempting MUST be discarded (RFC 9000 §6.2),
- servers MUST NOT receive VN; drop for robustness.
When negotiation succeeds the function picks the highest-priority
version that both peers support and re-runs the client handshake on
that version (resetting Initial-space keys and the Initial PN space,
queueing a fresh ClientHello via
start_client_handshake/1). When no common version exists the function returns{error, {transport_error, version_negotiation_error}, State}and the wrapper drives the connection into the draining state.
-spec maybe_update_peer_spin(nquic_packet:header(), nquic_packet:space(), nquic_packet_number:t(), nquic_protocol:state()) -> nquic_protocol:state().
-spec process_datagram(binary(), nquic_protocol:state(), [nquic_protocol:event()]) -> {ok, [nquic_protocol:event()], nquic_protocol:state()} | {error, term(), nquic_protocol:state()}.