# How XQUIC Handles 0-RTT Connection Resumption: Session Ticket Deep Dive

> Discover how XQUIC handles 0-RTT connection resumption. Learn about session ticket caching, early-data keys, and state machines for efficient client-side data transfer.

- Repository: [Alibaba/xquic](https://github.com/alibaba/xquic)
- Tags: deep-dive
- Published: 2026-02-24

---

**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:

```c
/* 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:

```c
/* 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:

```c
/* 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:

```c
/* 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:

```c
/* 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:

```c
/* 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`](https://github.com/alibaba/xquic/blob/main/src/tls/boringssl/xqc_ssl_if_impl.c) and [`src/tls/babassl/xqc_ssl_if_impl.c`](https://github.com/alibaba/xquic/blob/main/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:

```c
/* 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:

```c
/* -------------------------------------------------------------
   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 internal `tls->resumption` flag.
- **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_0RTT` to 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 either `xqc_conn_early_data_accept` to finalize the connection or `xqc_conn_early_data_reject` to 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`](https://github.com/alibaba/xquic/blob/main/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`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_conn.c) – Implements connection-level flags (`HAS_0RTT`, `0RTT_OK`) and the accept/reject handlers.
- [`src/transport/xqc_packet_out.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_packet_out.c) – Handles packet type selection and 0-RTT packet construction.
- [`src/tls/boringssl/xqc_ssl_if_impl.c`](https://github.com/alibaba/xquic/blob/main/src/tls/boringssl/xqc_ssl_if_impl.c) and [`src/tls/babassl/xqc_ssl_if_impl.c`](https://github.com/alibaba/xquic/blob/main/src/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.