# How to Implement Custom Congestion Control in XQUIC: A Complete Developer's Guide

> Learn to implement custom congestion control in XQUIC with this developer's guide. Define callbacks, set state size, and assign to connection settings for optimized performance.

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

---

**To implement custom congestion control in XQUIC, define a callback table implementing the `xqc_cong_ctrl_callback_t` interface, provide a state object size callback, and assign the table to `xqc_conn_settings_t.cong_ctrl_callback` before creating a connection.**

Alibaba's XQUIC library provides a modular transport architecture that allows developers to plug in custom congestion control algorithms via a standardized callback interface. By leveraging the `xqc_cong_ctrl_callback_t` structure defined in [`include/xquic/xquic.h`](https://github.com/alibaba/xquic/blob/main/include/xquic/xquic.h), you can inject your own logic for congestion window management, loss detection, and recovery while the transport layer handles packet scheduling and pacing automatically.

## Understanding the Congestion Control Interface

XQUIC isolates every congestion control algorithm behind the **`xqc_cong_ctrl_callback_t`** interface. When a connection is created, the transport layer examines `xqc_conn_settings_t.cong_ctrl_callback` to determine which algorithm to use. If no custom callback is provided, the system defaults to Cubic as implemented in [`src/congestion_control/xqc_cubic.c`](https://github.com/alibaba/xquic/blob/main/src/congestion_control/xqc_cubic.c).

The transport code selects the congestion control implementation during connection initialization in [`src/transport/xqc_send_ctl.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_send_ctl.c) (lines 156–165):

```c
if (conn->conn_settings.cong_ctrl_callback.xqc_cong_ctl_init_bbr) {
    send_ctl->ctl_cong_callback = &conn->conn_settings.cong_ctrl_callback;
} else if (conn->conn_settings.cong_ctrl_callback.xqc_cong_ctl_init) {
    send_ctl->ctl_cong_callback = &conn->conn_settings.cong_ctrl_callback;
} else {
    send_ctl->ctl_cong_callback = &xqc_cubic_cb;   /* default fallback */
}

```

The framework requires nine specific callbacks covering initialization, loss handling, acknowledgment processing, and window queries. The complete interface definition resides in [`include/xquic/xquic.h`](https://github.com/alibaba/xquic/blob/main/include/xquic/xquic.h) (lines 1019–1038).

## Step-by-Step Implementation Guide

### 1. Define Your Algorithm State

Create a header file defining your per-connection state structure. This object stores variables like congestion window, slow-start threshold, and recovery timestamps.

```c
/* my_cc.h */
#ifndef MY_CC_H
#define MY_CC_H

#include <xquic/xquic.h>

typedef struct {
    uint64_t cwnd;          /* Current congestion window in bytes */
    uint64_t ssthresh;      /* Slow-start threshold */
    xqc_usec_t recovery_ts; /* Start of current recovery epoch */
    xqc_send_ctl_t *ctl_ctx; /* Reference to send control context */
} my_cc_t;

#endif

```

### 2. Implement the Required Callbacks

Create a source file implementing all mandatory functions. The framework invokes these during specific transport events: packet loss, acknowledgments, and congestion window queries.

```c
/* my_cc.c */
#include "my_cc.h"
#include "src/transport/xqc_send_ctl.h"
#include <xquic/xquic.h>

static size_t
my_cc_size(void)
{
    return sizeof(my_cc_t);
}

static void
my_cc_init(void *cong_ctl, xqc_send_ctl_t *ctl_ctx, xqc_cc_params_t cc_params)
{
    my_cc_t *cc = (my_cc_t *)cong_ctl;
    cc->cwnd = XQC_kInitialWindow;  /* Default initial window */
    cc->ssthresh = UINT64_MAX;
    cc->recovery_ts = 0;
    cc->ctl_ctx = ctl_ctx;
}

static void
my_cc_on_lost(void *cong_ctl, xqc_usec_t lost_sent_time)
{
    my_cc_t *cc = (my_cc_t *)cong_ctl;
    
    if (lost_sent_time > cc->recovery_ts) {
        cc->recovery_ts = xqc_monotonic_timestamp();
        cc->cwnd = (cc->cwnd * 5) / 10;  /* Multiplicative decrease (0.5) */
        cc->ssthresh = cc->cwnd;
    }
}

static void
my_cc_on_ack(void *cong_ctl, xqc_packet_out_t *po, xqc_usec_t now)
{
    my_cc_t *cc = (my_cc_t *)cong_ctl;
    
    if (po->po_sent_time <= cc->recovery_ts) {
        return;  /* Ignore ACKs from recovery epoch */
    }
    
    if (cc->cwnd < cc->ssthresh) {
        cc->cwnd += po->po_used_size;  /* Slow start: additive growth */
    } else {
        cc->cwnd += (XQC_kMaxDatagramSize * po->po_used_size) / cc->cwnd;  /* Congestion avoidance */
    }
}

static uint64_t
my_cc_get_cwnd(void *cong_ctl)
{
    return ((my_cc_t *)cong_ctl)->cwnd;
}

static void
my_cc_reset_cwnd(void *cong_ctl)
{
    my_cc_t *cc = (my_cc_t *)cong_ctl;
    cc->cwnd = XQC_kInitialWindow;
    cc->recovery_ts = 0;
}

static int
my_cc_in_slow_start(void *cong_ctl)
{
    my_cc_t *cc = (my_cc_t *)cong_ctl;
    return cc->cwnd < cc->ssthresh;
}

static void
my_cc_restart_from_idle(void *cong_ctl, uint64_t arg)
{
    (void)cong_ctl;
    (void)arg;
}

static int
my_cc_in_recovery(void *cong_ctl)
{
    my_cc_t *cc = (my_cc_t *)cong_ctl;
    return cc->recovery_ts != 0;
}

```

### 3. Assemble the Callback Table

Export a global constant containing function pointers for all implemented callbacks. This table serves as the entry point for the transport layer.

```c
/* my_cc.c (continued) */
const xqc_cong_ctrl_callback_t my_cc_cb = {
    .xqc_cong_ctl_size = my_cc_size,
    .xqc_cong_ctl_init = my_cc_init,
    .xqc_cong_ctl_on_lost = my_cc_on_lost,
    .xqc_cong_ctl_on_ack = my_cc_on_ack,
    .xqc_cong_ctl_get_cwnd = my_cc_get_cwnd,
    .xqc_cong_ctl_reset_cwnd = my_cc_reset_cwnd,
    .xqc_cong_ctl_in_slow_start = my_cc_in_slow_start,
    .xqc_cong_ctl_restart_from_idle = my_cc_restart_from_idle,
    .xqc_cong_ctl_in_recovery = my_cc_in_recovery,
};

```

### 4. Register with Connection Settings

Assign your callback table to the connection settings before invoking `xqc_engine_connect`. The transport layer automatically allocates state memory using your `xqc_cong_ctl_size` callback and initializes it via `xqc_cong_ctl_init`.

```c
#include "my_cc.h"

xqc_conn_settings_t settings = xqc_conn_get_conn_settings_template(XQC_CONN_SETTINGS_DEFAULT);

/* Install custom congestion control */
settings.cong_ctrl_callback = my_cc_cb;

/* Optional: tune algorithm parameters */
settings.cc_params.max_cwnd = 2 * 1024 * 1024;  /* 2 MiB limit */

xqc_engine_t *engine = xqc_engine_create(&engine_settings, NULL);
xqc_connection_t *conn = xqc_engine_connect(engine, &local_addr, &peer_addr,
                                            &settings, NULL, XQC_CONN_TYPE_CLIENT);

```

## Reference Implementations and Key Files

When developing custom algorithms, examine these canonical implementations in the XQUIC repository:

- **[`src/congestion_control/xqc_new_reno.c`](https://github.com/alibaba/xquic/blob/main/src/congestion_control/xqc_new_reno.c)** (lines 29–40): Demonstrates a minimal compliant implementation with standard Reno logic
- **[`src/congestion_control/xqc_cubic.c`](https://github.com/alibaba/xquic/blob/main/src/congestion_control/xqc_cubic.c)** (line 251): Shows the default Cubic algorithm and window calculation patterns
- **[`src/congestion_control/xqc_bbr.c`](https://github.com/alibaba/xquic/blob/main/src/congestion_control/xqc_bbr.c)** (line 1223): Illustrates advanced pacing integration and bandwidth probing

The interface contract is defined in **[`include/xquic/xquic.h`](https://github.com/alibaba/xquic/blob/main/include/xquic/xquic.h)** (lines 1019–1038), while the callback dispatch logic lives in **[`src/transport/xqc_send_ctl.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_send_ctl.c)** (lines 156–165).

### Minimal Testing Example

For rapid prototyping, implement a fixed-window algorithm that maintains a constant congestion window regardless of network conditions:

```c
static uint64_t bigwin_get_cwnd(void *c) { return 10 * 1024 * 1024; }  /* 10 MiB */

const xqc_cong_ctrl_callback_t bigwin_cb = {
    .xqc_cong_ctl_size = bigwin_size,
    .xqc_cong_ctl_init = bigwin_init,
    .xqc_cong_ctl_get_cwnd = bigwin_get_cwnd,
    /* ... remaining no-op callbacks ... */
};

```

## Summary

- **XQUIC congestion control** is implemented via the `xqc_cong_ctrl_callback_t` interface requiring nine mandatory function pointers
- **State management** relies on your `xqc_cong_ctl_size` callback to allocate memory, followed by `xqc_cong_ctl_init` for initialization
- **Registration** occurs by assigning your callback table to `xqc_conn_settings_t.cong_ctrl_callback` before connection creation
- **Transport integration** happens automatically in [`xqc_send_ctl.c`](https://github.com/alibaba/xquic/blob/main/xqc_send_ctl.c), which invokes your callbacks during loss detection, ACK processing, and pacing decisions
- **Reference patterns** are available in NewReno ([`xqc_new_reno.c`](https://github.com/alibaba/xquic/blob/main/xqc_new_reno.c)), Cubic ([`xqc_cubic.c`](https://github.com/alibaba/xquic/blob/main/xqc_cubic.c)), and BBR ([`xqc_bbr.c`](https://github.com/alibaba/xquic/blob/main/xqc_bbr.c)) implementations

## Frequently Asked Questions

### What callbacks are mandatory for XQUIC congestion control?

You must implement all nine fields of the `xqc_cong_ctrl_callback_t` structure: `xqc_cong_ctl_size`, `xqc_cong_ctl_init`, `xqc_cong_ctl_on_lost`, `xqc_cong_ctl_on_ack`, `xqc_cong_ctl_get_cwnd`, `xqc_cong_ctl_reset_cwnd`, `xqc_cong_ctl_in_slow_start`, `xqc_cong_ctl_restart_from_idle`, and `xqc_cong_ctl_in_recovery`. The transport layer invokes these during specific connection lifecycle events, and missing implementations will cause undefined behavior or compilation failures.

### How does XQUIC allocate memory for custom congestion control state?

The framework calls your `xqc_cong_ctl_size` callback to determine the allocation size, then reserves that amount of memory per connection. During connection initialization, it passes this memory block to your `xqc_cong_ctl_init` function along with the send control context and congestion parameters. You cast this void pointer to your state structure type and initialize algorithm-specific variables.

### Can I change congestion control algorithms after a connection is established?

No. The XQUIC architecture selects the congestion control implementation during connection creation in [`xqc_send_ctl.c`](https://github.com/alibaba/xquic/blob/main/xqc_send_ctl.c) (lines 156–165) based on the `cong_ctrl_callback` field in the connection settings. Once assigned, the callback table remains fixed for the connection's lifetime. To use different algorithms, you must specify them when creating new connections.

### Where can I find working examples beyond NewReno?

Examine **[`src/congestion_control/xqc_cubic.c`](https://github.com/alibaba/xquic/blob/main/src/congestion_control/xqc_cubic.c)** for the default Cubic implementation including Hystart++ support, and **[`src/congestion_control/xqc_bbr.c`](https://github.com/alibaba/xquic/blob/main/src/congestion_control/xqc_bbr.c)** for a bandwidth-probing algorithm with pacing integration. Both files demonstrate production-ready patterns for window management, loss recovery, and RTT sampling that you can adapt for custom algorithms.