Imported from KellyKinyama/pure-dart-quic (
AGENTS.md). Install upstream withnpx skills add KellyKinyama/pure-dart-quic. Copyright stays with the author.
AGENTS.md — pure_dart_quic
A pure-Dart implementation of QUIC + TLS 1.3 + HTTP/3 + WebTransport. Research / demo quality (single connection, no congestion control). See README.md for the protocol feature matrix.
The repo has two layers:
- Modular public API under lib/src/ — what new code should consume. Re-exported from lib/pure_dart_quic.dart.
- Engine internals under lib/connection/, lib/h3/, lib/handshake/, lib/packet/, lib/cipher/, lib/frames/, lib/streams/ — the underlying QUIC/TLS/HTTP3 implementation that the modular API wraps.
Run / build / test
dart pub get
# Modular entry points (preferred)
dart run bin/server.dart # UDP 127.0.0.1:4433
dart run bin/client.dart
# Other modular protocol demos:
dart run bin/xmpp_server.dart # UDP 127.0.0.1:4435
dart run bin/xmpp_client.dart
dart run bin/moq_server.dart # UDP 127.0.0.1:4436
dart run bin/moq_client.dart
# Legacy entry points (kept; same engine)
dart run lib/connection/server/server2.dart
dart run lib/connection/client/client3.dart
dart analyze lib\src bin # modular layer is clean
dart analyze # engine has ~105 pre-existing info-level lints
dart test # only exercises stub calculate(); not meaningful for QUIC
There is no integration test harness — validate protocol changes by running the server and client (or a real HTTP/3 client) and reading print() traces.
Modular API layout
lib/
├── pure_dart_quic.dart # public façade (export-only)
└── src/
├── transport/
│ ├── udp/udp_transport.dart # UdpTransport, DartUdpTransport
│ └── quic/
│ ├── quic_connection.dart # QuicConnection, QuicStream
│ ├── quic_endpoint.dart # QuicServerEndpoint, QuicClientEndpoint
│ ├── server_connection.dart # wraps QuicServerSession
│ └── client_connection.dart # wraps QuicSession
└── app/
├── application_protocol.dart # ApplicationProtocol(Factory)
├── alpn_registry.dart # ALPN -> factory
├── h3/h3_protocol.dart # HTTP/3 + WebTransport (drives the QuicConnection API)
├── webtransport/ # WebTransport ALPN alias of H3 module
├── xmpp/xmpp_protocol.dart # XMPP-over-QUIC (length-prefixed stanzas on a single bidi stream)
├── media/media_protocol.dart # Media-over-QUIC (MoQ-style SETUP/SUBSCRIBE/ANNOUNCE control + DATAGRAM objects)
└── sip/ # SIP-over-QUIC (STUB)
Layered flow: UDP → QUIC → {H3, WebTransport, XMPP, Media, SIP} with ALPN-based protocol selection via AlpnRegistry.
When a registered factory matches the chosen ALPN, the engine's
internal HTTP/3 bootstrap is suppressed (externalAppProtocol = true)
and the protocol module drives SETTINGS, control stream, request
streams, and DATAGRAM I/O via QuicConnection. If no factory matches,
the engine still falls back to its legacy in-session HTTP/3 path.
Engine extraction status
Done (Phases 1–3 of the original TODO):
- ✅ Generic stream pipe extracted from the engine —
EngineQuicStreamperforms offset-based reassembly and per-stream FIN/RESET (see lib/src/transport/quic/engine_quic_stream.dart). - ✅ Engine emits
onIncomingStreamData(streamId, offset, data, fin)andonIncomingDatagram(data)events that the adapters surface viaQuicConnection.incomingStreams/QuicConnection.datagrams. - ✅ HTTP/3 + WebTransport moved to lib/src/app/h3/h3_protocol.dart and consumed via
QuicConnection. The legacy in-session H3 code still ships and still works when no ApplicationProtocolFactory is registered. - ✅ DATAGRAM (RFC 9221) implemented on both sessions via
sendDatagramFrame(payload)and surfaced throughQuicConnection.sendDatagram/QuicConnection.datagrams.
Still TODO:
- Wire real ALPN negotiation feedback through server_hello.dart — the modular layer now plumbs the advertised ALPN list into the ClientHello (see
QuicClientEndpoint.connect) and the server-sidechooseServerAlpnin tls_server_builder.dart acceptsxmpp-quicandmoq-00, but the negotiated ALPN string still isn't read back from the engine intoQuicConnection.alpn(the modular adapter labels the connection with whatever the caller passed).
The XMPP / Media modules now consume only the generic QuicConnection
(open uni/bidi streams, send/receive DATAGRAMs, observe inbound
streams). SIP remains registration-only.
Canonical entry points
| Role | New (modular) | Legacy (engine direct) |
|---|---|---|
Server main |
bin/server.dart | lib/connection/server/server2.dart |
Client main |
bin/client.dart | lib/connection/client/client3.dart |
| Coalesced UDP split | splitCoalescedPackets() in lib/constants.dart — call before per-packet handling |
Architecture map
| Layer | Directory | Responsibility |
|---|---|---|
| UDP / packet | lib/packet/ | long/short header parsing, header protection, packet decryption |
| Frames | lib/frames/ | CRYPTO, ACK, STREAM, etc. |
| TLS 1.3 | lib/handshake/ | ClientHello → Finished over CRYPTO frames |
| Crypto | lib/cipher/ | AES-GCM, HKDF, X25519, P-256, ECDSA, self-signed cert helpers |
| HTTP/3 + WT | lib/h3/ | control stream, QPACK static table, WebTransport CONNECT + DATAGRAM |
| Streams | lib/streams/ | reassembly of out-of-order STREAM fragments |
| Sessions | lib/connection/ | per-connection state machines (client + server) |
Project conventions / pitfalls
- Numeric file suffixes are iterations, not deprecation. tls_msg.dart (base class), tls_messages.dart (parser), tls_messages2.dart (alternate parser) all coexist. Before editing one,
grep_searchfor which session file imports it. - Logging is bare
print(). log.txt is a captured trace, not consumed by code; do not introduce a logger framework without being asked. - Coalesced datagrams must be split before per-packet processing — every receive loop in this repo calls
splitCoalescedPackets(dg.data)and iterates. - Peer address is set lazily on the server after the first datagram (see
peerSetflag in server2.dart); preserve this pattern when modifying the receive loop. false_secretsin pubspec.yaml whitelists lib/cipher/cert_utils.dart for pub.dev — it intentionally contains test key material; do not "clean it up".- No congestion control, no retry, no migration. Don't add stubs for production features unless the task requests them — keep the code research-grade.
- Dart SDK
^3.11.5, lints =package:lints/recommended.yaml. Rundart analyzebefore declaring a change done.