Spec coverage
quicz aims to implement the IETF QUIC transport protocol as defined by the QUIC WG at https://quicwg.org/.
Initial target documents:
- RFC 8999: Version-Independent Properties of QUIC
- RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport
- RFC 9001: Using TLS to Secure QUIC
- RFC 9002: QUIC Loss Detection and Congestion Control
- RFC 9369: QUIC Version 2
The verifiable transport implementation task matrix is maintained in
quic_transport_tasks.md. That document is the
working checklist for the first implementation scope; this file describes the
current implementation shape.
Phase 1: Minimal but correct subset
Section titled “Phase 1: Minimal but correct subset”- Single-path, IPv4 only
- QUIC v1 connection behavior by default, with configured QUIC v2 protected long-packet and Retry version use plus v2 packet/key/token primitives and RFC 9000 reserved-version greasing recognition available where noted
- Basic packet/header parsing and serialization
- One connection per UDP 4-tuple
- Basic stream support, send-side PING emission, STREAM and per-packet-number-space CRYPTO fragmentation, inbound out-of-order CRYPTO buffering with duplicate retransmission discard, out-of-order STREAM receive reassembly with duplicate retransmission discard, local RESET_STREAM and STOP_SENDING emission, inbound RESET_STREAM and STOP_SENDING handling, PATH_CHALLENGE response queuing, outbound PATH_CHALLENGE tracking with PTO-based retry, failure counting, matching PATH_RESPONSE validation with caller-committed endpoint route path update, modeled server anti-amplification send limiting with explicit peer-address validation, server-side Retry datagram issuance, first-client-Initial DCID length validation, server-Initial token validation, RFC 9000 Initial UDP datagram 1200-byte expansion/discard checks, HMAC-SHA256 address-bound expiring token generation/validation with originating-version binding, bounded token replay filtering, client-side Retry processing, Retry token consumption, handshake CID transport-parameter validation/export, local and peer-issued connection ID lifecycle handling, server-side HANDSHAKE_DONE and NEW_TOKEN issuing, client-only NEW_TOKEN storage, HANDSHAKE_DONE receive validation, handshake confirmation, RFC 9001 Initial discard after client Handshake send/server Handshake receive, and Handshake-space discard after valid client-side HANDSHAKE_DONE, server-side sendHandshakeDone, or backend-confirmed no-output Handshake drive, flow-control and stream-count limits, strict stream direction validation, max_idle_timeout handling, local/remote CONNECTION_CLOSE handling, and close-state handling
- Transactional processing for malformed in-memory frame payloads so partial receive, recovery, flow-control, and close-state updates roll back on failure
- Simplified loss detection and congestion control with automatic ACK generation, ACK range handling, unsent-packet ACK rejection, sent-packet tracking, ACK-driven STREAM and frame-payload/protected CRYPTO retransmission requeue, frame-payload packet-number-space ACK/recovery isolation, connection-level bytes-in-flight congestion admission, RTT estimate sharing, and PTO backoff across packet number spaces, modeled Initial/Handshake packet number space discard cleanup including ECN state, Initial/Handshake RTT ACK-delay suppression, Application ACK delay exponent and post-confirmation max_ack_delay handling, packet-threshold and time-threshold loss detection with deterministic timeout handling, NewReno slow-start/congestion-avoidance growth, NewReno-style recovery period handling, new-congestion-event one-packet recovery probe, persistent congestion response with min-RTT refresh, ACK_ECN CE congestion response, packet-number-space PTO PING/new-data/in-flight-CRYPTO/in-flight-STREAM timeout handling, and frame-payload ACK_ECN counter validation
Current implementation status
Section titled “Current implementation status”-
Address-validation tokens serialize and authenticate the originating QUIC version. Existing unversioned helpers keep QUIC v1 defaults, while
validateForVersion()and the connection/endpoint*ForVersion()helpers allow QUIC v2 callers to reject cross-version token reuse before replay state changes. -
RFC 9368
version_informationis represented as a typed transport parameter. Connections export the configured chosen and available versions, validate peer values according to endpoint role, allow reserved versions only in Available Versions, enforce server Version Information downgrade checks after a client reacts to Version Negotiation, classify parsed version-negotiation semantic close failures asVERSION_NEGOTIATION_ERROR, and expose the transport error code for callers that build the remaining Compatible Version Negotiation state machine.VersionCompatibilityandselectCompatibleVersion()model explicit, directional first-flight compatibility without assuming compatibility between any two QUIC versions.Connectionstores peer Version Information after successful transport-parameter application and provides server-side compatible-version apply helpers that require the selected version to match the connection’s configured chosen version. ThedriveCryptoBackendInSpaceWithCompatibleVersion()anddriveCryptoBackendInSpaceWithCompatibleVersionOrClose()wrappers route the same compatible Version Information policy throughCryptoBackendpeer transport-parameter bytes before backend output is pulled and report the selected version inCryptoBackendProgress. -
Client connections can validate one RFC 8999 Version Negotiation packet, ignore unsafe packets that contain the Original Version or mismatched CIDs, select a non-reserved mutual version from local
available_versions, carry that selection into a follow-up connection throughConfig.version_negotiation_selected_version, validate the server’s authenticated Version Information against it, and letTls13ClientEndpointreplace the owned transport while emitting the first route-bound follow-up Initial. If follow-up connection creation or Initial emission fails after route registration, the follow-up route is retired. -
Tls13ServerEndpointcan validate a Retry follow-up Initial on its switched route, consume the one-time address-validation token only after packet authentication, then drive Initial and Handshake TLS output with committed UDP routes. -
The pure Zig TLS 1.3 handshake always sends the QUIC
quic_transport_parametersextension, including empty parameter lists, and rejects ClientHello or EncryptedExtensions messages that omit it. -
The pure Zig TLS client rejects unrequested ServerHello extensions instead of silently continuing the handshake transcript.
-
The pure Zig TLS client rejects unrequested EncryptedExtensions extensions while still requiring QUIC transport parameters.
-
The pure Zig TLS certificate path rejects empty CertificateEntry values and the test builder no longer emits empty certificate entries.
-
The pure Zig TLS certificate path rejects per-entry Certificate extensions, matching the current ClientHello that does not request OCSP/SCT data.
-
The pure Zig TLS ClientHello emits
early_dataonly when a stored session ticket is also available for the matchingpre_shared_keyoffer. -
Implemented: QUIC varint helpers, minimal long/short header codecs with RFC 9369 QUIC v2 long-header packet type bit mapping, short-header spin-bit preservation, and explicit packet number truncation/reconstruction APIs, protected short-packet spin-bit peeking, RFC 9000 long/short packet envelope codecs with opaque payload handling, RFC 9000 packet number encoding selection/reconstruction, Retry packet codec, RFC 8999 Version Negotiation packet codec plus reserved-version greasing detection, client-side selection, and RFC 9368 downgrade-validation state, stateless reset datagram helpers with constant-time token matching and NEW_CONNECTION_ID token uniqueness checks, HMAC-SHA256 address-validation token helpers with originating-version binding plus bounded replay-filter helper with snapshot export/restore, in-memory endpoint
AddressValidationPolicywith versioned token issuance/validation, secret rotation,AddressValidationSecretSetexport/restore, and replay-filter snapshot export/restore, in-memory endpoint DCID/IPv4-tuple routing with unsupported-version Version Negotiation response generation, client Initial Source CID route registration, supported-version unknown-DCID Initial accept classification, accepted Initial Original DCID/server Initial SCID route registration, zero-length CID tuple routing, sequence/retire-prior-to/connection-handle route retirement, endpoint replacement-CID registration, inactive-CID stateless reset token lookup/datagram construction helpers, route/version-negotiation/reset/drop/accept receive classification, and per-UDP-path endpoint ECN state, typed RFC 9000/RFC 9368 transport parameter codec with reserved-parameter greasing helper/ignore, connection-level export/application helpers, and TLS extension byte encode/apply helpers, pluggableCryptoBackendcallback contract for CRYPTO byte-stream bridging, transport-parameter extension byte handoff, and mock Handshake/0-RTT/1-RTT traffic-secret handoff, typed RFC 9000/RFC 9368 transport error code helpers, RFC 9001 QUIC v1 and RFC 9369 QUIC v2 Initial secret/key/IV/header-protection key derivation, RFC 9001quic kukey-update secret/key/IV derivation, caller-owned and connection-installed short-packet key-phase state and selection, AEAD_AES_128_GCM payload protection helpers, protected long-packet seal/open helpers that use configured v2 wire versions and type bits, configured v2 Retry issue/process validation, v1/v2 Retry Integrity Tag helpers, and AES header-protection mask application helpers, and basic frame codecs for STREAM and CRYPTO with end-offset validation, PADDING, PING, ACK/ACK_ECN with additional ranges, RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS_BIDI/UNI with stream-count cap validation, DATA_BLOCKED, STREAM_DATA_BLOCKED, STREAMS_BLOCKED_BIDI/UNI with stream-count cap validation, NEW_TOKEN with non-empty token validation, NEW_CONNECTION_ID, RETIRE_CONNECTION_ID, PATH_CHALLENGE/PATH_RESPONSE, HANDSHAKE_DONE, and connection-close variants. -
Connectionimplements an in-memory stream send/receive skeleton with send-side PING emission, STREAM fragmentation, localresetStream()RESET_STREAM emission, localstopSending()STOP_SENDING emission, per-packet-number-space CRYPTO send/receive byte streams and backend driving, including local/peer transport-parameter byte exchange and mock Handshake/0-RTT/1-RTT traffic-secret installation, viasendCryptoInSpace()/recvCryptoInSpace()/pollTxInSpace()/driveCryptoBackendInSpace(), protected Initial/Handshake CRYPTO long-packet bridging viapollProtectedLongCryptoDatagramInSpace()/processProtectedLongDatagramInSpace()with first-client-Initial DCID length validation, server-Initial token validation, and RFC 9000 Initial UDP datagram 1200-byte expansion/discard checks, installed-key Handshake long-packet CRYPTO/ACK/PING bridging viapollProtectedHandshakeDatagramWithInstalledKeys()/processProtectedHandshakeDatagramWithInstalledKeys(), coalesced protected Initial/Handshake CRYPTO/ACK/PING plus caller-keyed or installed-key 0-RTT STREAM/RESET_STREAM/STOP_SENDING long-datagram transmit and receive routing viapollProtectedLongDatagram()/processProtectedLongDatagram()andpollProtectedZeroRttDatagram()/processProtectedZeroRttDatagram()/pollProtectedZeroRttDatagramWithInstalledKeys()/processProtectedZeroRttDatagramWithInstalledKeys(), caller-supplied and installed-key 1-RTT short protected PING/ACK/CRYPTO/HANDSHAKE_DONE/NEW_TOKEN/NEW_CONNECTION_ID/PATH_CHALLENGE/PATH_RESPONSE/RETIRE_CONNECTION_ID/MAX_*/BLOCKED/STREAM/RESET_STREAM/STOP_SENDING/CONNECTION_CLOSE transmit and receive viapollProtectedShortDatagram()/processProtectedShortDatagram(),pollProtectedShortDatagramWithInstalledKeys()/processProtectedShortDatagramWithInstalledKeys(), and explicit short-packet key-phase send/receive viapollProtectedShortDatagramWithKeyPhase()/processProtectedShortDatagramWithKeyUpdate(), plus the default Application-spacesendCrypto()/recvCrypto()wrappers, local transport parameter export vialocalTransportParameters()andencodeLocalTransportParameters()including configured localack_delay_exponent/max_ack_delay, serveroriginal_destination_connection_idafter the first client Initial, serverretry_source_connection_idafterissueRetryDatagram(), the first sent Initial SCID asinitial_source_connection_idwhen known, plus configured server-onlystateless_reset_tokenandpreferred_address, peer transport parameter application throughapplyPeerTransportParameters()andapplyPeerTransportParameterBytes()including Original DCID, Retry CID, peer Initial SCID validation, peermax_udp_payload_sizerecovery max_datagram_size/initial-cwnd resync,disable_active_migrationobservability, peer transport-parameter stateless reset token storage, peer preferred-address fixed storage, and peer ACK delay policy for recovery viaapplyPeerTransportParameters(),peerActiveMigrationDisabled(),peerStatelessResetToken(), andpeerPreferredAddress(), inbound out-of-order STREAM range buffering, duplicate retransmission discard, and contiguous reassembly, inbound RESET_STREAM handling, STOP_SENDING-to-RESET_STREAM response handling, PATH_CHALLENGE response queuing,sendPathChallenge()outbound challenge tracking with PTO-based retry throughcheckPathValidationTimeouts(), retry exhaustion reporting throughfailedPathValidationCount(), matching PATH_RESPONSE validation, modeled RFC 9000 server anti-amplification send limiting throughrecordPeerAddressBytesReceived(),antiAmplificationLimitRemaining(),validatePeerAddress(), server-side Retry datagram issuance throughissueRetryDatagram(), HMAC-SHA256 address-validation token issuing/validation throughissueAddressValidationToken()/validateAddressValidationToken()/validateAddressValidationTokenWithSecrets()plus endpoint peer-address binding and in-memory token lifecycle throughendpoint.Udp4Tuple.peerAddressValidationBinding()andendpoint.AddressValidationPolicy, client-side Retry datagram handling throughprocessRetryDatagram()withlatestRetryToken()/originalDestinationConnectionId()/retrySourceConnectionId()storage, and one-time server Retry token consumption throughissueRetryToken()/validateRetryToken(), peer-issued NEW_CONNECTION_ID tracking with retire_prior_to-driven RETIRE_CONNECTION_ID queuing, local NEW_CONNECTION_ID issuing throughissueConnectionId()with peer active-CID-limit enforcement and inbound RETIRE_CONNECTION_ID retirement, read-only stateless reset token detection throughdetectStatelessReset(), server-side HANDSHAKE_DONE and NEW_TOKEN issuing throughsendHandshakeDone()andissueNewToken(), client-side NEW_TOKEN storage, HANDSHAKE_DONE role validation, explicit handshake progress observability throughhandshakeState(), handshake confirmation, RFC 9001 Initial-space cleanup after a client Handshake packet send or server Handshake packet receive, and valid client-side HANDSHAKE_DONE-driven or server-side sendHandshakeDone-driven Handshake space/key discard, MAX_STREAM_DATA stream-state validation, basic connection and stream flow control with outbound DATA_BLOCKED/STREAM_DATA_BLOCKED/STREAMS_BLOCKED reporting, receive-side MAX_DATA/MAX_STREAM_DATA/MAX_STREAMS_BIDI/UNI credit refresh, and peer BLOCKED observability with receive-state creation/validation for STREAM_DATA_BLOCKED, stale-limit MAX retransmission, plus configured receive-window growth for DATA_BLOCKED/STREAM_DATA_BLOCKED and stream-count-window growth for STREAMS_BLOCKED_BIDI/UNI, bidirectional and unidirectional stream-count limits, remote close-state handling andpeerClose()diagnostics for CONNECTION_CLOSE/APPLICATION_CLOSE, local close emission/retransmission viacloseConnection()/closeApplication(),max_idle_timeoutexport/application throughConfig.max_idle_timeout_ms,effectiveIdleTimeoutMillis(),idleTimeoutDeadlineMillis(), andcheckIdleTimeouts()modeled closure, an explicitConnectionStatelifecycle for active/closing/draining/closed with 3x PTO close-state expiry, an explicitHandshakeStatelifecycle for initial/handshake/confirmed progress, explicitPacketNumberSpacehandling for Initial/Handshake/Application frame-payload ACK, explicitFramePacketTypevalidation for Initial, Handshake, 0-RTT, and 1-RTT frame-payload packet types, Initial/Handshake discard cleanup including installed Handshake key clearing throughdiscardPacketNumberSpace(), installed 0-RTT key cleanup throughdiscardZeroRttProtectionKeys(), ACK_ECN validation/CE congestion response, ACK delay scaling/capping, packet-threshold and time-threshold loss detection, NewReno slow-start/congestion-avoidance growth, NewReno-style recovery period handling, persistent congestion response, packet-number-space PTO PING timeout handling with queued-data/in-flight-CRYPTO/in-flight-STREAM probe selection, and recovery isolation, automatic ACK generation for ACK-eliciting payloads, ACK-only emission, ACK coalescing with PING/CRYPTO/HANDSHAKE_DONE/NEW_TOKEN/STREAM/PATH_CHALLENGE/PATH_RESPONSE/NEW_CONNECTION_ID/RETIRE_CONNECTION_ID/RESET_STREAM/STOP_SENDING/MAX_DATA/MAX_STREAM_DATA/MAX_STREAMS_BIDI/UNI frames when space allows, ACK-driven sent-packet tracking, ACK-driven frame-payload STREAM/CRYPTO and protected CRYPTO retransmission requeue, and a simplified recovery/congestion state object. -
applyPeerTransportParameterBytesOrClose()is an opt-in wrapper for peer transport-parameter extension bytes. It preserves the successful application behavior ofapplyPeerTransportParameterBytes(), but queues transport CONNECTION_CLOSE withTRANSPORT_PARAMETER_ERRORand CRYPTO frame_type for malformed extensions or invalid peer-parameter semantics before returningInvalidPacket. -
Aes128KeyPhaseStateprovides current/next 1-RTT key-phase state for one packet-protection direction. Callers can still own it directly throughpollProtectedShortDatagramWithKeyPhaseState()andprocessProtectedShortDatagramWithKeyPhaseState(). The connection can also installOneRttTrafficSecretsfrom a backend, derive local/peer AES-128-GCM packet-protection keys, and own local/peer key-phase state forpollProtectedShortDatagramWithInstalledKeys()andprocessProtectedShortDatagramWithInstalledKeys(). Receive state advances only after packet authentication and Application-frame processing succeed. Connection-owned local key-update initiation requires modeled handshake confirmation, rejects a second update until an Application ACK covers a packet sent with the new key phase, and rolls that confirmation state back if a later frame in the same payload is invalid. -
Config.enable_spin_bitenables a deterministic single-path 1-RTT spin-bit model on protected short packets. It defaults to false, so packetization keeps the prior false wire value; when enabled, server connections reflect the peer spin bit, client connections invert the latest server spin bit,nextOutgoingSpinBit()exposes the next value, andresetSpinBitForPath()clears state for a future path or destination-CID switch. -
endpoint.EndpointRoutermaps destination connection IDs to caller-owned connection handles and IPv4 UDP tuples. It can route long-header datagrams by the encoded DCID, write an RFC 8999 Version Negotiation response for unsupported long-header versions, register a client Initial Source CID before sending Initial so server Initial and Version Negotiation responses route back to the client connection, classify supported-version unknown-DCID Initial datagrams for future server accept loops with borrowed Original DCID, client Source CID, token, version, and path metadata, register the Original DCID plus server Initial SCID after accept without leaving partial routes on invalid/duplicate input, route short-header datagrams by registered-CID prefix matching, route zero-length CIDs by exact UDP tuple, reject unknown or ambiguous CIDs, reject stateless reset token reuse across different CIDs, store optional NEW_CONNECTION_ID sequence numbers, switch an Initial route from the original DCID to the Retry Source CID after Retry, commit caller-validated migration to a server preferred-address CID and UDP tuple, retire routes by CID, sequence number, retire_prior_to threshold, or caller connection handle including path-specific zero-CID routes, register a replacement CID and apply its retire_prior_to threshold as one endpoint policy operation, update a route to a caller-validated new path while rejecting stale path updates, enforceactive_migration_disabledas a deterministic path-change rejection, expose stateless reset tokens for inactive or retired CIDs while suppressing tokens for active routes on the same path, write stateless reset datagrams with caller-supplied unpredictable bytes when the reset is smaller than the triggering datagram, classify received datagrams as routed, version negotiation, stateless reset, accept_initial, or dropped, and derive a stable remote IPv4/UDP peer-address binding for address-validation tokens.endpoint.AddressValidationPolicyowns one active token secret, a bounded previous-secret set, and a bounded replay filter for in-memory endpoint token issuance, rotation, validation, and replay rejection; it can export/restoreAddressValidationSecretSetsnapshots and replay-filter snapshots for external persistence or worker distribution.endpoint.EcnPathPolicystores ECN validation state per UDP tuple so a migrated path starts fromunknowninstead of inheriting another path’s capable or failed state. Socket I/O, socket-backed reset emission, automatic path validation, production secret/replay storage, real IP ECN marking, andConnectionownership remain outside these helpers. -
Locally initiated bidirectional streams must be created with
openStream()beforesendOnStream().openStream()enforces the peer’s bidirectional stream limit until a larger MAX_STREAMS_BIDI frame is received. -
Locally initiated unidirectional streams must be created with
openUniStream()beforesendOnStream().openUniStream()enforces the peer’s unidirectional stream limit until a larger MAX_STREAMS_UNI frame is received. -
sendOnStream()accepts observed peer-initiated bidirectional streams so the in-memory echo examples can reply on the peer’s stream, and it accepts opened locally initiated unidirectional streams. It rejects unobserved peer-initiated streams, locally initiated streams that were not opened, peer-initiated unidirectional stream IDs, streams that already sent FIN, and flow-control-blocked writes. Connection, stream, and stream-count blocked writes queue the matching BLOCKED frame before returningFlowControlBlocked; queuedSTREAM_DATA_BLOCKEDis dropped before transmit if that send side later reaches FIN or reset. -
resetStream()aborts the send side of an opened local stream or an observed peer-initiated bidirectional reply stream. It queues oneRESET_STREAMwith the current send offset as final size, marks the send side closed, ignores duplicate local resets, and drops unsent queued STREAM data after the reset frame is emitted. -
stopSending()asks the peer to stop sending on an opened local bidirectional stream or an observed peer-initiated receive stream that is still in Recv or Size Known. It queues oneSTOP_SENDING, rejects send-only local unidirectional streams and unobserved peer streams, returnsStreamClosedonce all bytes through final size have already arrived, ignores duplicate local stop requests, drops queued STOP_SENDING frames before transmit if the receive side reaches Data Recvd or Reset Recvd first, and leaves the peer to respond with RESET_STREAM. -
Inbound
DATA_BLOCKED,STREAM_DATA_BLOCKED, andSTREAMS_BLOCKED_*frames update highest-observed peer blocked limits exposed bypeerDataBlockedLimit(),peerStreamDataBlockedLimit(),peerStreamsBlockedBidiLimit(), andpeerStreamsBlockedUniLimit().STREAM_DATA_BLOCKEDvalidates that the stream has a receive side at this endpoint, creates receive state before any STREAM data when valid, and rejects send-only, unopened local bidirectional, or receive stream-count-exhausted stream IDs. Once a stream final size is known,STREAM_DATA_BLOCKEDis discarded and no longer requeues or growsMAX_STREAM_DATAfor that stream. If the peer reports a limit below the current receive-side credit, the matchingMAX_DATA,MAX_STREAM_DATA, orMAX_STREAMS_*frame is requeued. If configured, current-limit peer BLOCKED reports grow MAX_DATA/MAX_STREAM_DATA from the receive byte windows and MAX_STREAMS_BIDI/UNI fromreceive_stream_count_window. Invalid multi-frame payloads roll these reports, receive-state creation, and any requeued or grown MAX frames back. -
recvOnStream()reads bidirectional streams and peer-initiated unidirectional receive streams. Locally initiated unidirectional stream IDs are send-only and are rejected on the receive API. When an inbound frame opens a higher-numbered receive stream, lower-numbered streams of the same type are also created and read as idle until data arrives. Successful reads increase the receive-side connection and stream credit by the consumed byte count, queueingMAX_DATAandMAX_STREAM_DATAframes while dropping obsolete lower queued limits. QueuedMAX_STREAM_DATAframes are also dropped before transmit once the stream leaves Recv by learning a final size or reset. Fully consumed peer-initiated FIN streams release one receive stream-count credit and queueMAX_STREAMS_BIDIorMAX_STREAMS_UNI, including zero-length FIN streams observed through the receive API. Peer-initiated reset streams release the same stream-count credit once the reset is observed throughrecvOnStream().recvStreamFinalSize()exposes the final size learned from STREAM FIN or RESET_STREAM, andrecvStreamFinished()reports successful FIN completion only after all bytes through the final size have been consumed. -
Inbound
MAX_STREAM_DATAupdates send credit only for streams where this endpoint has a send side and that send side has not already sent FIN. It rejects unopened locally initiated streams and receive-only peer unidirectional streams. For peer-initiated bidirectional streams, it can create both receive and send state before any STREAM data so future replies use the advertised credit. -
processDatagram()accepts modeled bidirectional STREAM/RESET_STREAM receive state and peer-initiated unidirectional STREAM/RESET_STREAM receive state in the Application packet number space;processDatagramInSpace()allows the same frame-payload skeleton to be exercised in Initial, Handshake, or Application packet number space, andprocessDatagramForPacketType()validates frame legality for Initial, Handshake, 0-RTT, or 1-RTT while mapping 0-RTT and 1-RTT to the shared Application packet number space. Initial and Handshake packet types reject RFC 9000-forbidden frame types, including STREAM, flow-control, connection-ID, path-validation, NEW_TOKEN, application close, and HANDSHAKE_DONE frames. The 0-RTT packet type accepts application frames including STREAM, RESET_STREAM, and STOP_SENDING, rejects ACK, ACK_ECN, CRYPTO, HANDSHAKE_DONE, NEW_TOKEN, PATH_RESPONSE, and RETIRE_CONNECTION_ID frames, and preserves Application-space ACK/recovery accounting; invalid multi-frame payloads roll back earlier state from the same payload.processDatagramOrClose(),processDatagramInSpaceOrClose(),processDatagramForPacketTypeOrClose(), and the protected long/short*OrClosereceive wrappers are opt-in APIs that keep that rollback boundary for success paths, but queue transport CONNECTION_CLOSE for classified frame encoding such as invalid ACK/ACK_ECN ranges and STREAMS_BLOCKED limits, packet-type violations including 0-RTT ACK/ACK_ECN frames, or semantic frame-processing failures such as ACK/ACK_ECN frames that acknowledge unsent packet numbers, conflicting STREAM data, and invalid stream-control frames before returningInvalidPacket. Stream receive handling buffers non-overlapping out-of-order STREAM ranges until gaps are filled, ignores identical duplicate retransmitted data that is already contiguous or exactly matches a pending range, trims already-received contiguous prefixes before appending new suffix bytes, ignores final-size-bounded late STREAM data once all data has been received, applies same-final-size RESET_STREAM to Size Known streams that still have gaps by accounting the remaining final-size credit and closing the receive side, ignores later STREAM data after RESET_STREAM when it stays within the known final size, and rejects inbound local bidirectional stream IDs that were not opened, inbound local unidirectional stream IDs, peer-initiated streams beyond receive stream-count limits, conflicting or ambiguous overlapping stream data before Data Recvd, data after final size, STREAM FINs that would change the reset final size, inconsistent RESET_STREAM final sizes, oversized frame payloads, and ACK/ACK_ECN frames for packet numbers that were never sent in the selected packet number space. -
EndpointConnectionLifecycleexposes direct and routed*OrClosevariants for protected Initial, long-header, 0-RTT, short-header, explicit key-phase, and installed-key receive helpers. These helpers route by endpoint CID/tuple first, then delegate to the matching connection close-propagating receive API, leaving the older lifecycle rollback-only receive APIs unchanged. -
ACK_ECN frames share ACK range handling and unsent-packet close classification with regular ACK frames for sent-packet recovery. The connection skeleton also validates modeled ECT(0)/ECT(1) sent packets against cumulative ACK_ECN counters per packet number space. A validated increase in the ECN-CE counter enters the same NewReno congestion recovery period used for loss without treating the acknowledged packet as lost; repeated CE increases for packets sent before that recovery start do not reduce the congestion window again. Validation failure records
EcnValidationState.failedinstead of closing the connection, because the frame-payload API does not yet own real IP ECN marking or network path identity. -
discardPacketNumberSpace()models the recovery side effect of discarding Initial or Handshake keys. It clears pending ACK, largest acknowledged packet, sent-packet tracking, queued/received CRYPTO state, bytes in flight, loss deadline, PTO backoff, and ECN validation counters/state for the discarded space, clears installed Handshake packet-protection keys when the Handshake space is discarded, rejects later frame-payload use of that space, and leaves Application data space intact. Client-side successful Handshake packet sends and server-side successful Handshake packet receives now trigger Initial-space discard only after the send/receive commit succeeds; failed sends, invalid payloads, and packet-authentication failures preserve Initial state. Server-sidesendHandshakeDone()discards Handshake state while keeping the 1-RTT HANDSHAKE_DONE frame queued, backend-confirmed no-output Handshake drives discard the same state, and installing Handshake keys after discard is rejected. Installed peer 0-RTT receive keys require explicitacceptZeroRtt()beforeprocessProtectedZeroRttDatagramWithInstalledKeys()can commit early data, andrejectZeroRtt()discards those keys before use.discardZeroRttProtectionKeys()explicitly clears installed early-data keys without discarding the shared Application packet number space; clients also clear installed 0-RTT keys when 1-RTT keys are installed, and servers clear installed 0-RTT keys only after a 1-RTT short packet authenticates and its Application-frame payload is accepted. -
Initial, Handshake, and Application CRYPTO byte streams have independent offsets, send queues, receive buffers, out-of-order pending buffers, pending ACKs, sent-packet tracking, and rollback behavior. CRYPTO receive handling bounds accepted end offsets with
Config.max_crypto_buffer_size, buffers gaps until contiguous bytes are available torecvCryptoInSpace(), ignores identical retransmissions against already-buffered contiguous bytes or exact pending ranges, and rejects conflicting overlaps. Close-on-error receive APIs map configured CRYPTO receive-buffer violations toCRYPTO_BUFFER_EXCEEDED.driveCryptoBackendInSpace()can give local transport-parameter extension bytes to a caller-suppliedCryptoBackend, apply peer transport-parameter extension bytes returned by that backend, install returned Handshake, 0-RTT, and 1-RTT traffic secrets, deliver contiguous CRYPTO bytes, queue backend-produced bytes back into the matching CRYPTO send stream, and mark the modeled handshake confirmed when the backend reports completion. If a Handshake-space backend drive confirms the handshake without queuing outbound CRYPTO, it discards Handshake state and installed Handshake keys in the same call; if outbound Handshake CRYPTO was queued, the space is preserved so the flight can be sent first. This is still a frame-payload TLS bridge hook; it does not yet run a real TLS 1.3 backend. -
RTT updates decode ACK Delay with the peer
ack_delay_exponent. Initial and Handshake ACK Delay are ignored, and each valid largest-acknowledged RTT sample updates connection-level RTT estimates across non-discarded packet number spaces. OnceHANDSHAKE_DONEorconfirmHandshake()marks the handshake confirmed, Application-space RTT updates cap decoded ACK Delay by the peermax_ack_delay. -
ACK processing applies simplified packet-threshold and time-threshold loss detection in the selected packet number space. When an unacknowledged sent packet is at least three packet numbers older than the largest newly acknowledged packet, or when its
sent_time + 9/8 * max(latest_rtt, smoothed_rtt)deadline has elapsed with the 1ms granularity floor, it is removed from sent tracking and reported to the recovery state as lost. The recovery state grows the congestion window by newly acknowledged bytes during slow start, and by at least one byte usingmax_datagram_size * bytes_acked / congestion_windowduring congestion avoidance.applyPeerTransportParameters()now resyncs the recoverymax_datagram_size; if peermax_udp_payload_sizelowers the effective send size, all packet-number-space recovery states reset their congestion window to the recalculated initial congestion window for that smaller size. A NewReno-style recovery period prevents repeated congestion-window reductions for losses or ECN-CE congestion events involving packets sent before recovery started, and ACKs for those packets do not grow the congestion window. When a loss or ECN-CE signal starts a new recovery period and there is already ack-eliciting data queued, the selected packet number space gets a one-packet recovery probe allowance so the retransmission can be emitted even when bytes in flight now fills the reduced congestion window. If a contiguous lost interval sent after the first RTT sample spans the RFC 9002 persistent congestion duration, the congestion window is reduced to the minimum window; when that ACK also produced the newest RTT sample,min_rttis refreshed to that sample and re-synced across packet number spaces. Non-contiguous lost packet numbers spanning the same duration stay on the normal NewReno recovery path.lossDetectionDeadlineMillis()exposes the next modeled loss deadline andcheckLossDetectionTimeouts()applies due deadlines under a controlled clock.ptoDeadlineMillis()exposes the simplified PTO deadline,lossDetectionTimerDeadlineMillis()selects the aggregate loss-time-before-PTO timer deadline across packet number spaces, andserviceLossDetectionTimer()is the connection helper for servicing that due aggregate timer.EndpointLossDetectionTimersowns endpoint/event-loop scheduling across caller connection handles, refreshes each handle after timer service, and is used by the socket-backed UDP loss/PTO examples to arm, service, and disarm protected-packet timers after ACK or loss cleanup. PTO handling uses one connection-level backoff count, resets it on acknowledged ack-eliciting packets except client Initial ACKs or Initial/Handshake space discard, disarms server PTO while anti-amplification-limited with no send credit, re-arms and services expired PTO whenrecordPeerAddressDatagramReceived()grants new send credit after a received datagram, and still prefers already queued ack-eliciting data, in-flight CRYPTO, then Application STREAM as the probe before queueing PING probes in due Initial, Handshake, or Application packet number spaces. Full socket-owned connection lifecycle and TLS-owned protected-packet timer integration remain pending. -
Inbound
STOP_SENDINGis accepted only for streams where this endpoint has a send side. For peer-initiated bidirectional streams, it can create receive state before any STREAM data, resets only the local send side, and leaves the receive side open for later peer data. It uses the same send-side close andRESET_STREAMqueueing behavior asresetStream(). -
Inbound
NEW_TOKENis accepted only by client connections and stored as opaque future address-validation data up toConfig.max_stored_new_tokens. Servers can create and later validate HMAC-SHA256 address-bound, version-bound, expiring NEW_TOKEN values withissueAddressValidationToken(),issueAddressValidationTokenForVersion(),validateAddressValidationToken(),validateAddressValidationTokenForVersion(), orvalidateAddressValidationTokenWithSecrets(); endpoint code can useAddressValidationPolicyto issue tokens forUdp4Tuple.peerAddressValidationBinding(), validate across a rotated secret set, export/restore the active/previous token secrets throughAddressValidationSecretSet, export/restore replay-filter fingerprints throughReplayFilterSnapshot, and reject reuse after restore. Server-side receipt fails the payload as invalid and rolls back any earlier changes from that payload. -
Server-side
sendHandshakeDone()marks the modeled handshake confirmed, discards Handshake packet-number-space state plus installed Handshake keys, and leaves the 1-RTT HANDSHAKE_DONE frame queued until a laterpollTx()or protected short-packet send commits. InboundHANDSHAKE_DONEis accepted only by client connections, marks the modeled handshake confirmed, and after the full payload is accepted discards the same Handshake state. Server-side receipt fails the payload as invalid and rolls back any earlier changes from that payload. -
Invalid multi-frame payloads roll back state changed earlier in that payload, including handshake confirmation, CRYPTO receive and pending buffers, stream receive buffers, RESET_STREAM state, stored NEW_TOKEN values, queued PATH_RESPONSE/RETIRE_CONNECTION_ID/RESET_STREAM values, outstanding PATH_CHALLENGE values and retry metadata, peer-issued and local connection ID retired flags, MAX_DATA/MAX_STREAMS_BIDI/UNI updates, selected-space pending ACK state, selected-space sent-packet recovery, packet/time-threshold loss, and ECN validation state, and close-state changes.
-
Current
pollTx/processDatagrambehavior moves unencrypted QUIC frame payload bytes.pollTxmay emit ACK-only payloads, queued CONNECTION_CLOSE/APPLICATION_CLOSE, PING, CRYPTO, HANDSHAKE_DONE, NEW_TOKEN, PATH_CHALLENGE, PATH_RESPONSE, NEW_CONNECTION_ID, RETIRE_CONNECTION_ID, MAX_/BLOCKED, RESET_STREAM, or STREAM payloads, or coalesce a pending ACK when space allows. Successful send/receive activity refreshes the modeled idle timeout deadline; invalid frame payloads do not. On server connections with an unvalidated peer address,pollTx()andpollTxInSpace()enforce the modeled 3x anti-amplification budget across packet number spaces untilvalidatePeerAddress()is called directly or a matching Retry token is consumed. Server connections can issue a Retry datagram withissueRetryDatagram(), which records the Retry Source Connection ID for local transport-parameter export and registers the token for one-time validation.issueAddressValidationToken()creates HMAC-SHA256 address-bound, version-bound, expiring Retry or NEW_TOKEN values,AddressValidationPolicycan issue those tokens forUdp4Tuple.peerAddressValidationBinding(), validate them across current/previous secrets, and reject already-used token fingerprints through its replay filter, whilevalidateAddressValidationToken()/validateAddressValidationTokenWithSecrets()validate the token kind, originating version, peer address, lifetime, and MAC before lifting the anti-amplification limit; Retry tokens are also consumed once from the pending Retry-token set. Client connections can validate a Retry datagram withprocessRetryDatagram(); a stored Retry token is reused by protected Initial packetization when the explicit Initial token argument is empty, andapplyPeerTransportParameters()validatesoriginal_destination_connection_idplusretry_source_connection_idagainst the stored Retry CIDs. The first successfully sent protected client Initial records the Original Destination Connection ID for later server transport-parameter validation. The first successfully sent protected Initial records this endpoint’s Initial Source Connection ID throughlocalInitialSourceConnectionId(), andlocalTransportParameters()exports it asinitial_source_connection_idonce known. The first successfully opened protected client Initial records the Original Destination Connection ID for serverlocalTransportParameters().original_destination_connection_idexport. The first successfully opened protected Initial records the peer’s Initial Source Connection ID throughpeerInitialSourceConnectionId(), andapplyPeerTransportParameters()validatesinitial_source_connection_idagainst that recorded value when it is known. Initial and Handshake CRYPTO, ACK-only, and PING packets can also be emitted as protected long packets, coalesced throughpollProtectedLongDatagram(), and received either one packet at a time throughprocessProtectedLongDatagramInSpace()or as a coalesced long datagram throughprocessProtectedLongDatagram(); the matching*OrClosevariants queue CONNECTION_CLOSE after authenticated protected plaintext frame errors. Handshake packets can also use connection-installed keys throughpollProtectedHandshakeDatagramWithInstalledKeys()/processProtectedHandshakeDatagramWithInstalledKeys(). Caller-keyed or installed-key 0-RTT STREAM/RESET_STREAM/STOP_SENDING packets use the Application packet number space and 0-RTT frame rules throughpollProtectedZeroRttDatagram()/processProtectedZeroRttDatagram(),pollProtectedZeroRttDatagramWithInstalledKeys()/processProtectedZeroRttDatagramWithInstalledKeys(), orpollProtectedLongDatagram()/processProtectedLongDatagram()withkeys.zero_rtt; the 0-RTT receive helpers also expose close-propagating*OrClosevariants. Protected 1-RTT short PING/ACK/CRYPTO/HANDSHAKE_DONE/NEW_TOKEN/NEW_CONNECTION_ID/PATH_CHALLENGE/PATH_RESPONSE/RETIRE_CONNECTION_ID/MAX_/BLOCKED/STREAM/RESET_STREAM/STOP_SENDING/CONNECTION_CLOSE datagrams can be emitted throughpollProtectedShortDatagram()and opened throughprocessProtectedShortDatagram()with caller-supplied keys;processProtectedShortDatagramOrClose()and the key-update/installed-key*OrClosevariants queue CONNECTION_CLOSE for authenticated protected plaintext frame errors. When enabled, the short-header spin bit is updated only after packet authentication and Application-frame processing succeed. -
Not implemented yet: real TLS 1.3 backend integration, endpoint datagram APIs beyond the current caller-keyed/installed-key protected long/short packet bridges, full TLS-owned live 1-RTT key-update scheduling and old-key discard, full TLS 0-RTT replay policy, remaining TLS-triggered key discard scheduling beyond the implemented Initial/HANDSHAKE_DONE hooks, full TLS backend transcript ownership for transport parameters, socket-owned protected-packet RFC 9002 loss/PTO timer lifecycle integration, socket-backed UDP 4-tuple connection ownership, socket-owned endpoint Retry issuance/accept loops, automatic socket-backed migration to a server preferred_address, production endpoint token-secret/replay storage and distribution around exported snapshots, ECN validation bound to real IP-header marking, automatic socket-backed path validation binding to endpoint path identity, full address-validation policy, full DCID routing lifecycle, socket-owned connection ID replacement policy, socket-backed stateless reset emission for unknown CIDs, full QUIC v2 behavior beyond Initial key derivation, long-header type-bit mapping, configured protected long-packet/Retry version use, Retry integrity helpers, token version binding, and RFC 9368 version-information primitives, and full path migration policy.
-
0-RTT resumption (RFC 8446 §2.2 + RFC 9001 §4.6): the pure-Zig TLS 1.3 stack implements the full client-side key chain (resumption_master_secret -> PSK -> early_traffic_secret -> binder_key -> psk_binder), ClientHello carries pre_shared_key + early_data extensions, the CryptoBackend
pull_zero_rtt_traffic_secretshook exposes the 0-RTT early traffic secret, and the Connection installs the local 0-RTT packet-protection key and emits early-data STREAM. The server validates the ClientHello pre_shared_key identity and binder vectors without truncating identities, derives the early secret from a configured PSK, constant-time-verifies the psk_binder, and only on a matching binder derives the client early traffic secret and installs it as the peer 0-RTT key;acceptZeroRtt()then gatesprocessProtectedZeroRttDatagramWithInstalledKeys()for early-data delivery. The server emits a post-handshake NewSessionTicket after the handshake; the client validates its handshake length, ticket vector, mandatory extensions vector, and local storage bound before deriving a resumption PSK and storing the ticket, closing the PSK source for 0-RTT.rejectZeroRtt()discards installed peer 0-RTT keys before use; clients also clear 0-RTT keys when 1-RTT keys are installed.
Further phases will extend towards full RFC coverage.