How XQUIC Handles 0-RTT Connection Resumption: Session Ticket Deep Dive
XQUIC enables zero-round-trip-time (0-RTT) connection resumption by caching TLS session tickets client-side, deriving early-data encryption keys, and providing transport-layer state machines that automatically handle server acceptance or rejection of early data.
0-RTT connection resumption allows clients to send application data immediately without waiting for the TLS handshake to complete, significantly reducing latency for subsequent connections. In the alibaba/xquic repository, this implementation follows the QUIC-TLS integration defined in RFC 9001, coupling OpenSSL/BoringSSL primitives with a custom transport-layer state machine that manages encryption keys, packet typing, and buffer lifecycle.
How Session Tickets Enable 0-RTT Resumption
XQUIC relies on session tickets cached from previous connections to establish cryptographic context for early data transmission.
Loading Cached Session Tickets on the Client
When initializing a client TLS instance via xqc_tls_create, the library checks for user-supplied session ticket data in the configuration structure. If cfg->session_ticket and cfg->session_ticket_len are present, the client loads the PEM-encoded ticket and validates its expiration before installing it into the SSL context:
/* src/tls/xqc_tls.c:227-230 */
if (xqc_tls_cli_set_session_data(tls, cfg->session_ticket,
cfg->session_ticket_len) == XQC_OK) {
tls->resumption = XQC_TRUE; // mark resumption
xqc_ssl_enable_max_early_data(ssl); // enable 0-RTT in OpenSSL/BoringSSL
}
The function xqc_tls_cli_set_session_data parses the ticket and invokes SSL_set_session to restore the previous session state, while xqc_ssl_enable_max_early_data configures the underlying SSL library to permit early data transmission.
The Resumption Flag
The TLS object maintains an internal state flag that persists throughout the connection lifecycle:
/* src/tls/xqc_tls.c:57-59 */
xqc_bool_t resumption; // true if client supplied a valid session ticket
This resumption flag acts as the primary gatekeeper; all subsequent 0-RTT operations check this boolean before proceeding with early-data key derivation or packet construction.
Determining 0-RTT Readiness
Before transmitting early data, XQUIC verifies that cryptographic keys are available and the connection state permits 0-RTT packets.
Early-Data Key Derivation
The library checks readiness by confirming the 0-RTT transmission key has been derived from the session ticket:
/* src/tls/xqc_tls.c:21-30 */
xqc_bool_t
xqc_tls_is_ready_to_send_early_data(xqc_tls_t *tls)
{
if (tls->resumption == XQC_FALSE) {
return XQC_FALSE; // no ticket → no early data
}
/* ready when the 0-RTT TX key has been derived */
return xqc_tls_is_key_ready(tls, XQC_ENC_LEV_0RTT,
XQC_KEY_TYPE_TX_WRITE);
}
The TX key becomes available after the TLS layer derives the 0-RTT traffic secret in xqc_tls_set_write_secret, which is triggered by QUIC method callbacks during the initial handshake phase.
Connection-Level Validation
Transport modules query readiness through a connection-level helper that wraps the TLS check:
/* src/transport/xqc_conn.c:4249-4251 */
xqc_bool_t
xqc_conn_is_ready_to_send_early_data(xqc_connection_t *conn)
{
return xqc_tls_is_ready_to_send_early_data(conn->tls);
}
All packet construction logic calls xqc_conn_is_ready_to_send_early_data to determine whether to label outgoing packets as 0-RTT or buffer them for 1-RTT transmission.
Constructing 0-RTT Packets
When building packets, the transport layer evaluates the readiness state to select the appropriate packet type and encryption level:
/* src/transport/xqc_packet_out.c:22-31 */
int support_0rtt = xqc_conn_is_ready_to_send_early_data(conn);
...
if (!(conn->conn_flag & XQC_CONN_FLAG_CAN_SEND_1RTT)) {
if ((conn->conn_type == XQC_CONN_TYPE_CLIENT) &&
(conn->conn_state == XQC_CONN_STATE_CLIENT_INITIAL_SENT) &&
support_0rtt) {
pkt_type = XQC_PTYPE_0RTT; // mark as 0-RTT packet
conn->conn_flag |= XQC_CONN_FLAG_HAS_0RTT;
stream->stream_flag |= XQC_STREAM_FLAG_HAS_0RTT;
} else {
/* not allowed → buffer for later 1-RTT */
buff_reset = XQC_TRUE;
}
}
This pattern appears consistently across all send-side functions—including xqc_write_reset_stream_to_packet and xqc_write_stream_frame_to_packet—ensuring that any frame type (STREAM, RESET_STREAM, DATAGRAM) can be encapsulated in 0-RTT packets when the capability is enabled.
Server-Side Processing and Acceptance
After the TLS handshake completes, the server determines whether to accept or reject the early data transmitted during the 0-RTT phase.
Querying Early-Data Acceptance
XQUIC queries the underlying TLS library to determine the server's decision:
/* src/tls/xqc_tls.c:7-18 */
xqc_tls_early_data_accept_t
xqc_tls_is_early_data_accepted(xqc_tls_t *tls)
{
if (tls->type == XQC_TLS_TYPE_CLIENT && !tls->resumption) {
return XQC_TLS_NO_EARLY_DATA; // client never sent early data
}
return xqc_ssl_is_early_data_accepted(tls->ssl)
? XQC_TLS_EARLY_DATA_ACCEPT
: XQC_TLS_EARLY_DATA_REJECT;
}
Platform-specific implementations in src/tls/boringssl/xqc_ssl_if_impl.c and src/tls/babassl/xqc_ssl_if_impl.c map this to the appropriate SSL library calls (SSL_early_data_accepted for OpenSSL, SSL_EARLY_DATA_ACCEPTED for BoringSSL).
Handling Acceptance or Rejection
During xqc_conn_handshake_complete, the connection evaluates the acceptance status and executes the appropriate cleanup path:
/* src/transport/xqc_conn.c:77-90 */
if ((conn->conn_flag & XQC_CONN_FLAG_HANDSHAKE_COMPLETED) &&
((conn->conn_type == XQC_CONN_TYPE_CLIENT && conn->conn_flag & XQC_CONN_FLAG_HAS_0RTT) ||
conn->conn_type == XQC_CONN_TYPE_SERVER) &&
!(conn->conn_flag & XQC_CONN_FLAG_0RTT_OK) &&
!(conn->conn_flag & XQC_CONN_FLAG_0RTT_REJ))
{
int accept = xqc_tls_is_early_data_accepted(conn->tls);
if (accept == XQC_TLS_EARLY_DATA_REJECT) {
xqc_conn_early_data_reject(conn);
} else if (accept == XQC_TLS_EARLY_DATA_ACCEPT) {
xqc_conn_early_data_accept(conn);
}
}
On acceptance, xqc_conn_early_data_accept clears the XQC_CONN_FLAG_0RTT_OK flag, frees buffered 0-RTT datagrams, and discards write buffers for all streams via xqc_destroy_write_buff_list.
On rejection, xqc_conn_early_data_reject clears the HAS_0RTT flag, drops queued 0-RTT packets, and resets stream states to ensure data is retransmitted securely within 1-RTT packets.
Buffer Management and Cleanup
XQUIC maintains packet out queues for 0-RTT data that require lifecycle management if the server rejects early data. When rejection occurs, the library traverses the pending packet list and either:
- Moves packets to the regular 1-RTT queue for retransmission after key updates, or
- Drops them entirely (for frames like RESET_STREAM that are unsafe to retransmit).
This cleanup logic resides in xqc_conn_early_data_reject and associated packet-out handling functions that monitor conn->conn_flag & XQC_CONN_FLAG_HAS_0RTT to identify 0-RTT buffers requiring processing.
Practical Implementation Example
Below is a minimal client implementation demonstrating how to enable 0-RTT resumption using the XQUIC API:
/* -------------------------------------------------------------
1️⃣ Load a previously saved session ticket (e.g. from a file)
------------------------------------------------------------- */
char *ticket_buf = NULL;
size_t ticket_len = 0;
read_ticket_from_storage(&ticket_buf, &ticket_len); // user-provided
/* -------------------------------------------------------------
2️⃣ Fill the TLS configuration for the client
------------------------------------------------------------- */
xqc_engine_t *engine = xqc_engine_create(&engine_cfg);
xqc_tls_config_t tls_cfg = {0};
tls_cfg.session_ticket = ticket_buf; // cached ticket
tls_cfg.session_ticket_len = ticket_len;
tls_cfg.alpn = "hq-29"; // example ALPN
/* -------------------------------------------------------------
3️⃣ Create a connection (the library will set tls->resumption)
------------------------------------------------------------- */
xqc_connection_t *conn;
xqc_engine_connect(engine, &conn,
"example.com", 4433, // host / port
NULL, 0, NULL, 0, // local/peer addr (filled by engine)
&tls_cfg, NULL); // tls cfg, user_data = NULL
/* -------------------------------------------------------------
4️⃣ If xqc_conn_is_ready_to_send_early_data(conn) is true,
you can start sending streams immediately.
------------------------------------------------------------- */
if (xqc_conn_is_ready_to_send_early_data(conn)) {
xqc_stream_t *stream;
xqc_conn_create_stream(conn, &stream, XQC_STREAM_TYPE_UNI);
xqc_stream_write(stream, (uint8_t *)"Hello, 0-RTT!", 14);
}
When the server accepts or rejects the early data, XQUIC automatically invokes xqc_conn_early_data_accept or xqc_conn_early_data_reject; the application does not require additional logic to handle these transitions.
Summary
- Session ticket resumption is enabled when clients supply cached tickets to
xqc_tls_cli_set_session_data, setting the internaltls->resumptionflag. - Readiness checks occur via
xqc_conn_is_ready_to_send_early_data, which verifies 0-RTT key derivation before permitting packet construction. - Packet typing automatically assigns
XQC_PTYPE_0RTTto outgoing frames when the connection state and cryptographic readiness permit early data transmission. - Server decisions are queried through
xqc_tls_is_early_data_accepted, triggering eitherxqc_conn_early_data_acceptto finalize the connection orxqc_conn_early_data_rejectto requeue data in 1-RTT buffers. - Automatic cleanup ensures that rejected 0-RTT data is either retransmitted securely or dropped, maintaining protocol correctness and data integrity.
Frequently Asked Questions
What triggers 0-RTT capability in XQUIC?
0-RTT capability is triggered when a client provides a valid session ticket via the session_ticket and session_ticket_len fields in xqc_tls_config_t. The library sets tls->resumption = XQC_TRUE and enables max_early_data in the underlying SSL context, allowing the TLS layer to derive 0-RTT encryption keys before the handshake completes.
How does XQUIC handle rejected 0-RTT data?
When a server rejects early data, XQUIC invokes xqc_conn_early_data_reject, which clears the XQC_CONN_FLAG_HAS_0RTT flag and processes the packet out queue to either move packets to the 1-RTT queue for retransmission or drop them entirely. This ensures that no data is lost while maintaining forward secrecy requirements by retransmitting sensitive frames only within the authenticated 1-RTT channel.
Which source files contain the core 0-RTT logic?
The primary files implementing 0-RTT resumption are:
src/tls/xqc_tls.c– Contains the resumption flag, early-data readiness checks (xqc_tls_is_ready_to_send_early_data), and acceptance queries (xqc_tls_is_early_data_accepted).src/transport/xqc_conn.c– Implements connection-level flags (HAS_0RTT,0RTT_OK) and the accept/reject handlers.src/transport/xqc_packet_out.c– Handles packet type selection and 0-RTT packet construction.src/tls/boringssl/xqc_ssl_if_impl.candsrc/tls/babassl/xqc_ssl_if_impl.c– Provide platform-specific SSL interface implementations for early data functions.
Is 0-RTT enabled by default in XQUIC?
No, 0-RTT is not enabled by default. The capability requires explicit configuration by supplying a cached session ticket during connection setup. Without this ticket, the tls->resumption flag remains false, and xqc_conn_is_ready_to_send_early_data returns XQC_FALSE, forcing all data to transmit within standard 1-RTT packets after the handshake completes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →