TsgcQUICClient is a low-level QUIC transport client (RFC 9000) that opens a QUIC connection to a server over UDP and exchanges raw bytes over QUIC streams.
QUIC is the transport protocol standardised in RFC 9000, with its TLS integration in RFC 9001 and its loss detection and congestion control in RFC 9002. It is the transport HTTP/3 runs on. Compared with TCP plus TLS it changes four things that matter to an application:
TsgcQUICClient exposes this transport directly, as raw bytes on streams. It does not speak HTTP/3 or any other application protocol: use TsgcHTTP3Client when you want HTTP semantics over QUIC. Reach for TsgcQUICClient when you are carrying your own protocol over QUIC, or testing a QUIC endpoint.
Scope worth knowing before you start. The component is a thin client over the QUIC transport, and two of its limits surprise people:
h3 and cannot be configured. 0-RTT resumption and connection migration are implemented in the underlying transport class but are not surfaced as properties on this component.
The library can drive QUIC through either of two engines. The engine follows SSLQUICOptions.IOHandler:
iohOpenSSL (the default), if the OpenSSL build exports OSSL_QUIC_client_method, the builtin engine (qmBuiltin) is used. Here OpenSSL 3.5 or later owns the whole QUIC connection and the library drives it through the OpenSSL stream API.SSL_set_quic_tls_cbs), the sgc engine (qmCustom) is used as the fallback. This is the pure Pascal QUIC stack in the sgcQUIC_* units: it builds and parses the packets itself and uses OpenSSL only to drive the TLS 1.3 handshake. If neither API is available, for example with a quictls build, initialising the connection raises an exception.iohNativeTLS the sgc engine is always used and the TLS 1.3 handshake runs on the native TLS engine, with session tickets, resumption and 0-RTT. The packet protection is native too, so OpenSSL is not loaded at all.
With the OpenSSL 3.5 binaries bundled with the demos qmBuiltin is selected. IsOpenSSL_QUIC_Available and IsOpenSSL_QUIC_TLS_Available, declared in sgcIdSSLOpenSSLHeaders, report which of the two OpenSSL APIs the loaded library exports. The engines compare as follows:
| Feature | qmBuiltin (OpenSSL 3.5+) | qmCustom (sgc engine) |
|---|---|---|
| Connect, disconnect, send on the default stream | Yes | Yes |
| EnableMultiStream | Yes | Yes |
| OpenStream / WriteStreamData | Yes | Yes |
| OnQUICStreamData (the read path) | Yes, after EnableMultiStream | Yes, after EnableMultiStream |
| QUICOptions.Settings tuning | Mostly negotiated by OpenSSL, values not consulted | Yes, drives the Pascal stack |
See QUIC for the full engine comparison.
1. Create a TsgcQUICClient and set Host and Port of the QUIC server. QUIC runs over UDP, so Port is a UDP port, commonly 443.
2. Configure TLS through SSLQUICOptions. Verification is off by default, so turn VerifyCertificate on and point RootCertFile at your CA for anything but a throwaway test. With verification on the certificate must now also match the host you are connecting to, so a self-signed certificate, or one issued for a different name, is rejected where it used to be accepted.
3. Handle OnQUICConnect, OnQUICDisconnect and OnQUICException. Handling OnQUICException is not optional: connect failures do not raise, they are reported there, and Connect merely returns False.
4. Open the connection with Connect (blocking, returns a result) or Start / Active := True (non-blocking).
5. If you need to receive anything, call EnableMultiStream once connected and handle OnQUICStreamData. To send, use WriteData for the default stream, or OpenStream plus WriteStreamData for a specific one.
oClient := TsgcQUICClient.Create(nil);
oClient.Host := 'www.example.com';
oClient.Port := 443;
oClient.SSLQUICOptions.VerifyCertificate := True;
oClient.SSLQUICOptions.RootCertFile := 'ca.pem';
oClient.SSLQUICOptions.ConnectTimeout := 10000;
oClient.OnQUICConnect := OnQUICConnectEvent;
oClient.OnQUICStreamData := OnQUICStreamDataEvent;
oClient.OnQUICException := OnQUICExceptionEvent;
if oClient.Connect then
oClient.WriteData('Hello QUIC');
| Name | Description |
|---|---|
| Active | Opens or closes the QUIC connection to the remote server. |
| Host | IP address or DNS name of the QUIC server the client will connect to. |
| Port | UDP port used to connect to the QUIC server. |
| SSLQUICOptions | TLS 1.3 settings: certificates, peer verification and the handshake timeout. Verification is off by default. |
| QUICOptions | Transport tuning for the QUIC connection, such as PMTU and flow control windows. |
| Name | Description |
|---|---|
| Connect | Opens the QUIC connection and waits until it is established or the timeout expires. |
| Disconnect | Closes the QUIC connection to the server. |
| WriteData | Sends data to the server over the default QUIC stream. |
| EnableMultiStream | Switches the live connection into multi-stream mode. Required before anything can be received. |
| OpenStream | Opens a new client-initiated QUIC stream and returns it, or nil on failure. |
| Name | Description |
|---|---|
| OnQUICConnect | Fires once the QUIC connection has been established and the client is ready to send data. |
| OnQUICStreamData | Fires with the bytes read from a QUIC stream. Inert until EnableMultiStream has been called. |
| OnQUICDisconnect | Fires when the QUIC connection has been closed, reporting the close code and reason. |
| OnQUICException | Fires when an exception is raised on the QUIC connection. The only way to learn why a connect failed. |
Connect to a QUIC server, enable multi-stream so data can be received, send a message on a dedicated stream and log everything that comes back.
uses
sgcHTTP, sgcQUIC_Client, sgcQUIC_Classes;
procedure TForm1.Start;
begin
FClient := TsgcQUICClient.Create(nil);
FClient.Host := 'www.example.com';
FClient.Port := 443;
// TLS 1.3. Verification is off by default, turn it on.
FClient.SSLQUICOptions.VerifyCertificate := True;
FClient.SSLQUICOptions.RootCertFile := 'ca.pem';
// the handshake timeout, 10000 ms is already the default
FClient.SSLQUICOptions.ConnectTimeout := 10000;
FClient.OnQUICConnect := OnQUICConnectEvent;
FClient.OnQUICStreamData := OnQUICStreamDataEvent;
FClient.OnQUICDisconnect := OnQUICDisconnectEvent;
FClient.OnQUICException := OnQUICExceptionEvent;
// Connect returns False without raising, the reason arrives on OnQUICException
if not FClient.Connect(10000) then
Log('connect failed');
end;
procedure TForm1.OnQUICConnectEvent(Sender: TObject; const Connection: TsgcQUICConnection);
var
oStream: TsgcQUICClientStream;
begin
Log('connected');
// required before OnQUICStreamData will ever fire
FClient.EnableMultiStream;
oStream := FClient.OpenStream; // bidirectional
if Assigned(oStream) then
FClient.WriteStreamData(oStream, TEncoding.UTF8.GetBytes('Hello QUIC'))
else
Log('could not open a stream, is the builtin OpenSSL QUIC engine active?');
end;
procedure TForm1.OnQUICStreamDataEvent(Sender: TObject; aStreamId: Int64;
aUnidirectional: Boolean; const aData: TBytes);
begin
// raised on the read thread, do not touch the UI directly here
Log(Format('stream %d: %d bytes', [aStreamId, Length(aData)]));
end;
procedure TForm1.OnQUICDisconnectEvent(Sender: TObject; const Connection: TsgcQUICConnection;
Code: Int64; const Reason: string);
begin
if Code = 0 then
Log('closed cleanly')
else
Log(Format('closed, code %d: %s', [Code, Reason]));
end;
procedure TForm1.OnQUICExceptionEvent(Sender: TObject; const Connection: TsgcQUICConnection;
const E: Exception);
begin
Log('error: ' + E.Message);
end;
{$DEFINE SGC_PACK_HTTP3} inside the {$IFDEF SGC_EDT_ALL} block, it is delivered as {.$DEFINE SGC_PACK_HTTP3}. Enabling it defines SGC_QUIC, SGC_HTTP3 and SGC_WEBTRANSPORT, then rebuild the packages.{$IFDEF SGC_INDY_LIB}, so the SGC_INDY_LIB build is required.TsgcQUICClient component itself, sgcQUIC_Client for TsgcQUICClientStream, and sgcQUIC_Classes for TsgcQUICConnection in the event handlers.libssl-3 and libcrypto-3) for the builtin QUIC engine, which is the engine that ships. Earlier OpenSSL 3.x builds have no QUIC support and raise an exception when the connection is initialised.PlatformNotSupportedException.