Skip to content

vacp2p/nim-lsquic

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, 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.

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.
  • Connection.close() and Stream.close() perform a graceful shutdown. abort() is the hard-stop path.

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/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

No description, website, or topics provided.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHEv2
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

 
 
 

Contributors