nim-lsquic is a Nim wrapper around lsquic with a Chronos-based async API for QUIC clients, servers, connections, and streams.
The repository vendors lsquic as a submodule and exposes a higher-level Nim interface on top of the generated FFI bindings. The current public API is focused on transport primitives: runtime setup, TLS configuration, dialing, listening, connection lifecycle, and bidirectional QUIC streams.
lsquic is a mature QUIC and HTTP/3 implementation in C. This project wraps the QUIC pieces in a more Nim-friendly API built around Chronos.
If you want to open QUIC connections and exchange stream data from Nim without writing directly against the C interface, this repository is the layer for that. It is not a full HTTP/3 framework at the moment; the public API is centered on QUIC transport functionality.
- Async
QuicClientandQuicServertypes built on Chronos futures Listener,Connection, andStreamabstractions over the underlyinglsquicengine- TLS setup via
TLSConfig - Pluggable certificate verification with
CustomCertificateVerifierorInsecureCertificateVerifier - Generated low-level bindings in
lsquic/lsquic_ffi.nim - CI coverage on Linux, macOS, and Windows across Nim 2.0 and 2.2
For development or local use, clone the repository with submodules and install the Nim dependencies:
git clone --recursive https://github.com/vacp2p/nim-lsquic.git
cd nim-lsquic
nimble install- Nim
>= 2.0.0 - A working C/C++ toolchain
zlib- On Windows, a Clang-compatible toolchain and
nasmare required in CI
The project links through a C++ linker profile on non-Windows platforms, so g++ or clang++ must be available.
The main entry point is lsquic.nim:
import std/[sequtils, sets]
import chronos
import results
import lsquic
proc toBytes(s: string): seq[byte] =
toSeq(s.toOpenArrayByte(0, s.high))
proc readAll(stream: Stream): Future[seq[byte]] {.async.} =
var buf = newSeq[byte](1024)
while true:
let n = await stream.readOnce(buf)
if n == 0:
break
result.add(buf[0 ..< n])
const
certPem = staticRead("cert.pem")
keyPem = staticRead("key.pem")
proc main() {.async.} =
initializeLsquic()
defer:
cleanupLsquic()
let alpn = ["echo"].toHashSet()
let server = QuicServer.new(
TLSConfig.new(
certificate = toBytes(certPem),
key = toBytes(keyPem),
alpn = alpn,
)
)
let devVerifier: CertificateVerifier = InsecureCertificateVerifier.init()
let client = QuicClient.new(
TLSConfig.new(
certificate = toBytes(certPem),
key = toBytes(keyPem),
alpn = alpn,
certVerifier = Opt.some(devVerifier),
)
)
let listener = server.listen(initTAddress("127.0.0.1:0"))
defer:
await allFutures(client.stop(), listener.stop())
let accepting = listener.accept()
let clientConn = await client.dial(listener.localAddress())
let serverConn = await accepting
let serverTask = proc() {.async.} =
let stream = await serverConn.incomingStream()
let request = await stream.readAll()
await stream.write(request)
await stream.close()
serverConn.close()
asyncSpawn serverTask()
let stream = await clientConn.openStream()
let message = @['p'.byte, 'i'.byte, 'n'.byte, 'g'.byte]
await stream.write(message)
await stream.close()
let reply = await stream.readAll()
doAssert reply == message
clientConn.close()
waitFor main()The snippet expects cert.pem and key.pem in PEM format and embeds them at compile time with staticRead. It also reuses the same certificate material on both sides purely to keep the example short.
InsecureCertificateVerifier is only appropriate for local development and tests. In real deployments, use CustomCertificateVerifier or another verifier that checks the peer certificate chain according to your trust model.
- Call
initializeLsquic()before creating clients or servers. cleanupLsquic()is idempotent and can be safely called during shutdown.- Server-side
TLSConfigmust include both a certificate and a private key. - Client and server ALPN values must match or the handshake will fail.
Connection.close()andStream.close()perform a graceful shutdown.abort()is the hard-stop path.
For more complete usage patterns, see:
nimble test
nimble test_releaseThe CI matrix exercises:
- Linux amd64 and i386
- Linux with GCC 14
- macOS arm64
- Windows amd64
- Nim 2.0 (
refc) and Nim 2.2 (refcandorc)
nimble formatIf the vendored lsquic headers change, regenerate lsquic/lsquic_ffi.nim:
./build.shThe script installs futhark@0.15.0, regenerates the binding file, and appends the project-specific prelude and extras.
| Module | Description |
|---|---|
lsquic |
Top-level import that re-exports the main public API |
lsquic/lsquic |
Process-wide lsquic initialization and cleanup |
lsquic/client |
QuicClient creation, dialing, and transport shutdown |
lsquic/server |
QuicServer, Listener, binding, and accept() |
lsquic/connection |
Connection lifecycle, stream creation, certificate access |
lsquic/stream |
Async stream reads, writes, close, and abort |
lsquic/tlsconfig |
TLS configuration plus PEM-to-X509/PKey helpers |
lsquic/certificateverifier |
Base, custom, and insecure certificate verifier adapters |
lsquic/lsquic_ffi |
Generated low-level bindings to the vendored native libraries |
Licensed and distributed under either of
- MIT license: LICENSE-MIT or http://opensource.org/licenses/MIT
or
- Apache License, Version 2.0, (LICENSE-APACHEv2 or http://www.apache.org/licenses/LICENSE-2.0)
at your option. These files may not be copied, modified, or distributed except according to those terms.