Skip to content

About

quic implemented in nim with lsquic

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Repository files navigation

nim-lsquic

Tests License: Apache 2.0 License: MIT

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.

Background

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.

Features

  • Async QuicClient and QuicServer types built on Chronos futures
  • Listener, Connection, and Stream abstractions over the underlying lsquic engine
  • TLS setup via TLSConfig
  • Pluggable certificate verification with CustomCertificateVerifier or InsecureCertificateVerifier
  • Generated low-level bindings in lsquic/lsquic_ffi.nim
  • CI coverage on Linux, macOS, and Windows across Nim 2.0 and 2.2

Install

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

Requirements

  • Nim >= 2.0.0
  • A working C/C++ toolchain
  • zlib
  • On Windows, a Clang-compatible toolchain and nasm are required in CI

The project links through a C++ linker profile on non-Windows platforms, so g++ or clang++ must be available.

Quick Start

The main entry point is lsquic.nim:

import std/sequtils
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"]

  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.

Usage Notes

  • Call initializeLsquic() before creating clients or servers.
  • cleanupLsquic() is idempotent and can be safely called during shutdown.
  • Server-side TLSConfig must include both a certificate and a private key.
  • Client and server ALPN values must match or the handshake will fail.
  • Stream.close() shuts down the stream's write side gracefully. Connection.close() can reset open bidirectional streams; Connection.abort() terminates the connection without those per-stream resets. Neither connection method drains open streams or guarantees delivery of pending data. Complete application-level exchanges before closing the connection. Connection teardown without a received stream FIN is reported as a stream error, not normal EOF.
  • UDP sockets request an 8 MiB receive buffer by default. Pass QuicSocketConfig(receiveBufferBytes: 0) to keep the OS default, or set another byte value; Linux may cap the effective size at net.core.rmem_max.

QUIC Configuration

nim-lsquic initializes LSQUIC with its native client or server defaults, then applies a few interoperability defaults internally. Public configuration support is split by ownership:

Area Nim API LSQUIC setting
UDP receive buffer QuicSocketConfig.receiveBufferBytes OS SO_RCVBUF
TLS certificate, ALPN, verification TLSConfig TLS callbacks
Handshake timeout QuicEngineConfig.handshakeTimeout es_handshake_to
Idle and no-progress timeouts idleTimeout, noProgressTimeout es_idle_timeout, es_noprogress_timeout
Keepalive pingPeriod es_ping_period
Bidirectional streams initialMaxStreamsBidi es_init_max_streams_bidi
Connection flow control initialMaxData, maxConnectionFlowControlWindow es_init_max_data, es_max_cfcw
Stream flow control initialMaxStreamDataBidiLocal, initialMaxStreamDataBidiRemote, maxStreamFlowControlWindow corresponding es_* fields
Congestion control congestionControl es_cc_algo
Migration allowMigration es_allow_migration
Path MTU discovery enablePathMtuDiscovery es_dplpmtud

All engine fields are optional. Leaving a field unset preserves nim-lsquic's role-specific behavior, which lets nim-libp2p expose one configuration object for both dialing and listening. noProgressTimeout is disabled (0) for both roles unless set, so the application decides when to close a quiet connection. Duration values must match LSQUIC's precision: whole microseconds for handshakes and whole seconds for the other timers. Invalid values are rejected when a client, server, or endpoint is created.

let engineConfig = QuicEngineConfig(
  idleTimeout: Opt.some(60.seconds),
  pingPeriod: Opt.some(20.seconds),
  initialMaxStreamsBidi: Opt.some(256'u32),
)
let server = QuicServer.new(tlsConfig, engineConfig = engineConfig)
let client = QuicClient.new(tlsConfig, engineConfig = engineConfig)

For more complete usage patterns, see:

Development

Run tests

nimble test
nimble test_release

The CI matrix exercises:

  • Linux amd64 and i386
  • Linux with GCC 14
  • macOS arm64
  • Windows amd64
  • Nim 2.0 (refc) and Nim 2.2 (refc and orc)

Format

nimble format

Regenerate the FFI bindings

If the vendored lsquic headers change, regenerate lsquic/lsquic_ffi.nim:

./build.sh

The script installs futhark@0.15.0, regenerates the binding file, and appends the project-specific prelude and extras.

Modules

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/socketconfig UDP socket configuration
lsquic/engine_config Typed LSQUIC engine configuration
lsquic/certificateverifier Base, custom, and insecure certificate verifier adapters
lsquic/lsquic_ffi Generated low-level bindings to the vendored native libraries

License

Licensed and distributed under either of

or

at your option. These files may not be copied, modified, or distributed except according to those terms.

About

quic implemented in nim with lsquic

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages