QUIC

QUIC is the UDP-based, TLS 1.3-encrypted, multiplexed transport defined in RFC 9000, and the transport that HTTP/3 runs on.

Introduction

QUIC is a general-purpose transport protocol specified across three core documents:

RFCTitleWhat it defines
RFC 9000QUIC: A UDP-Based Multiplexed and Secure TransportThe transport itself: packets, frames, streams, connection IDs, flow control, connection lifecycle and migration.
RFC 9001Using TLS to Secure QUICHow TLS 1.3 is embedded in the handshake, the key schedule, packet protection and header protection.
RFC 9002QUIC Loss Detection and Congestion ControlAcknowledgements, RTT estimation, loss detection, probe timeouts and the congestion controller.

Why QUIC exists

QUIC was designed to fix problems that are structural in TCP plus TLS, and that cannot be fixed without changing the transport itself.

QUIC engines

This library contains two QUIC engines. They are represented by the TsgcQUICMode enumeration declared in sgcSSL_QUIC.pas:

TsgcQUICMode = (qmCustom, qmBuiltin);

The engine is not set directly. It follows the TLS IOHandler of the QUIC options (SSLQUICOptions on the QUIC client, TLSOptions on the HTTP/3 client, QUICOptions.SSLOptions on the servers):

With the OpenSSL binaries shipped with the library you get qmBuiltin. The servers report the engine they started on in the read-only QUICMode property.

With iohNativeTLS, the sgc engine protects packets natively: packet and header protection (AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305), key derivation, key update and the Retry integrity tag run on sgcCrypto instead of OpenSSL, per connection and without any define. Together with the native TLS 1.3 handshake, QUIC and HTTP/3 run without OpenSSL at all, see SSL without OpenSSL. With iohOpenSSL, the packet protection follows sgcCryptoBackend (or the SGC_NATIVE_CRYPTO define).

Engine comparison

The table below reflects what the code actually does, not what QUIC is capable of in principle.

 qmBuiltinqmCustom
Transport implementationOpenSSL 3.5+ native QUIC. The library drives it through OSSL_QUIC_client_method, OSSL_QUIC_server_method, SSL_new_listener and SSL_accept_connection.Pascal QUIC stack inside the library (the sgcQUIC_* units: packets, frames, congestion control, flow control), with the TLS 1.3 handshake driven through the OpenSSL 3.5 QUIC TLS API or the native TLS engine.
Selected whenIOHandler = iohOpenSSL and OpenSSL exports the built-in QUIC stack. This is the default with mainline OpenSSL 3.5+.IOHandler = iohNativeTLS, or OpenSSL exports the QUIC TLS API but not the built-in QUIC stack.
Used by TsgcQUICClient and TsgcHTTP3ClientYesYes
Used by TsgcQUICServer and TsgcHTTP3ServerYesYes. An empty Host listens dual-stack, on IPv4 and IPv6, as on the built-in engine.
Multiple streams per connection through the component APIYes. OpenStream maps onto SSL_new_stream, and peer-opened streams can be harvested.Yes. OpenStream, WriteStreamData and ConcludeStream work with per-stream flow control and retransmission. The HTTP/3 client opens its control and QPACK streams and reuses one QUIC connection for later requests.
WebTransportYes. WebTransport needs multiple streams per connection, so it needs this engine.No.

In short: qmBuiltin is the default engine with mainline OpenSSL 3.5+. qmCustom is the fallback when the built-in stack is not available, and the engine used with iohNativeTLS.

Streams

A QUIC connection is a container for streams. A stream is an ordered, flow-controlled sequence of bytes, and streams are independent of one another, which is what removes transport head-of-line blocking. Streams come in two shapes:

Every stream has a 62-bit stream ID. The two least significant bits of the ID encode who opened the stream and whether it is bidirectional, which is the layout the library mirrors in TsgcQUICStreamTypes (sgcQUIC_Types.pas):

Low 2 bitsEnumeration valueMeaning
0x00qcstClientBidirectionalClient-initiated, bidirectional
0x01qcstServerBidirectionalServer-initiated, bidirectional
0x02qcstClientUnidirectionalClient-initiated, unidirectional
0x03qcstServerUnidirectionalServer-initiated, unidirectional

Stream IDs are assigned in increasing order within each of those four categories, and how many streams a peer may open is bounded by the transport parameters exchanged during the handshake. The QUIC components expose a single default stream out of the box. Additional streams are opt-in through EnableMultiStream and OpenStream, and as noted above they require the builtin engine.

Components

ComponentDescription
TsgcQUICClientLow-level QUIC client. Opens a QUIC connection to a server and exchanges raw application bytes over QUIC streams. Use it when you want QUIC as a transport without HTTP semantics.
TsgcQUICServerLow-level QUIC server. Listens for QUIC connections and exchanges raw application bytes over QUIC streams.

The HTTP/3 components build on this layer. TsgcHTTP3Server descends directly from TsgcQUICServer and adds the HTTP/3 framing, settings and request handling on top of the QUIC connections its ancestor accepts. TsgcHTTP3Client is a separate component rather than a descendant of TsgcQUICClient, but it drives the same underlying QUIC transport. See HTTP/3.

Quick start

The client connects to a host and port, then writes application bytes on the default stream. Full property, method and event documentation lives on the component pages.

oClient := TsgcQUICClient.Create(nil);
oClient.Host := 'localhost';
oClient.Port := 4433;
oClient.OnQUICConnect := OnQUICConnectHandler;
if oClient.Connect(10000) then
  oClient.WriteData('hello over QUIC');

The server binds a UDP port, loads a certificate and key, and then raises an event for every buffer it receives.

oServer := TsgcQUICServer.Create(nil);
oServer.Port := 4433;
oServer.QUICOptions.SSLOptions.CertFile := 'cert.pem';
oServer.QUICOptions.SSLOptions.KeyFile := 'key.pem';
oServer.OnQUICData := OnQUICDataHandler;
oServer.Start;

QUIC always runs over UDP, so make sure the UDP port is open on any firewall in the path. A port that works for TCP is not automatically reachable over UDP.

The listener binds the address in Host. A value holding a colon is an IPv6 literal, and any other value, an IPv4 literal or a host name, is resolved over both families. An empty Host binds the dual-stack wildcard, so one listener serves IPv4 and IPv6 peers on the same port.

Requirements

RequirementDetail
EditionAll-Access. The SGC_PACK_HTTP3 pack that enables QUIC sits in the SGC_EDT_ALL block of sgcVer.inc.
Pack defineSGC_PACK_HTTP3. It ships commented out in sgcVer.inc, so it must be enabled explicitly before QUIC is compiled in.
Feature definesWith SGC_PACK_HTTP3 and SGC_INDY_LIB both enabled, sgcVer.inc defines SGC_QUIC, SGC_HTTP3 and SGC_WEBTRANSPORT. The QUIC components are compiled under SGC_QUIC.
IndySGC_INDY_LIB is required. The QUIC defines are nested inside an SGC_INDY_LIB guard, so QUIC is not available in an Indy-free build.
OpenSSLOpenSSL 3.5 or later. The server components need the native QUIC listener (SSL_new_listener, SSL_accept_connection) that was introduced in 3.5, and raise an exception on older versions.
UnitssgcHTTP.pas declares the components. The transport lives in the sgcQUIC_* units and the OpenSSL glue in sgcSSL_QUIC.pas.

To enable QUIC, edit sgcVer.inc and uncomment the pack define inside the All-Access block:

{$IFDEF SGC_EDT_ALL} { ALL-ACCESS }
  {$DEFINE SGC_PACK_HTML}
  {$DEFINE SGC_PACK_HTTP3}
  {.$DEFINE SGC_PACK_WEBRTC}
{$ENDIF}

See also