quiche is an implementation of the QUIC transport protocol and HTTP/3 as specified by the IETF. It provides a low level API for processing QUIC packets and handling connection state. The application is responsible for providing I/O (e.g. sockets handling) as well as an event loop with support for timers.
Note that it is very experimental and unstable software, and many features are still in development. Refer to the Status section to see what is currently implemented.
A live QUIC server based on quiche is available at https://quic.tech:4433/
to be used for experimentation.
For more information on how quiche came about and some insights into its design you can read a post on Cloudflare's (where this library is used in production) blog that goes into some more detail.
The first step in establishing a QUIC connection using quiche is creating a configuration object:
let config = quiche::Config::new(quiche::VERSION_DRAFT18).unwrap();
This is shared among multiple connections and can be used to configure a QUIC endpoint.
On the client-side the connect()
utility function can be used to create
a new connection, while accept()
is for servers:
// Client connection.
let conn = quiche::connect(Some(&server_name), &scid, &mut config).unwrap();
// Server connection.
let conn = quiche::accept(&scid, None, &mut config).unwrap();
Using the connection's recv()
method the application can process
incoming packets that belong to that connection from the network:
loop {
let read = socket.recv(&mut buf).unwrap();
let read = match conn.recv(&mut buf[..read]) {
Ok(v) => v,
Err(quiche::Error::Done) => {
// Done reading.
break;
},
Err(e) => {
// An error occurred, handle it.
break;
},
};
}
Outgoing packet are generated using the connection's send()
method
instead:
loop {
let write = match conn.send(&mut out) {
Ok(v) => v,
Err(quiche::Error::Done) => {
// Done writing.
break;
},
Err(e) => {
// An error occurred, handle it.
break;
},
};
socket.send(&out[..write]).unwrap();
}
When packets are sent, the application is responsible for maintaining a
timer to react to time-based connection events. The timer expiration can be
obtained using the connection's timeout()
method.
let timeout = conn.timeout();
The application is responsible for providing a timer implementation, which
can be specific to the operating system or networking framework used. When
a timer expires, the connection's on_timeout()
method should be called,
after which additional packets might need to be sent on the network:
// Timeout expired, handle it.
conn.on_timeout();
// Send more packets as needed after timeout.
loop {
let write = match conn.send(&mut out) {
Ok(v) => v,
Err(quiche::Error::Done) => {
// Done writing.
break;
},
Err(e) => {
// An error occurred, handle it.
break;
},
};
socket.send(&out[..write]).unwrap();
}
After some back and forth, the connection will complete its handshake and will be ready for sending or receiving application data.
Data can be sent on a stream by using the stream_send()
method:
if conn.is_established() {
// Handshake completed, send some data on stream 0.
conn.stream_send(0, b"hello", true).unwrap();
}
The application can check whether there are any readable streams by using
the connection's readable()
method, which returns an iterator over all
the streams that have outstanding data to read.
The stream_recv()
method can then be used to retrieve the application
data from the readable stream:
if conn.is_established() {
// Iterate over readable streams.
let streams: Vec<u64> = conn.readable().collect();
for stream_id in streams {
// Stream is readable, read until there's no more data.
while let Ok((read, fin)) = conn.stream_recv(stream_id, &mut buf) {
println!("Got {} bytes on stream {}", read, stream_id);
}
}
}
Have a look at the examples/ directory for more complete examples on how to use the quiche API, including examples on how to use quiche in C/C++ applications (see below for more information).
quiche exposes a thin C API on top of the Rust API that can be used to more easily integrate quiche into C/C++ applications (as well as in other languages that allow calling C APIs via some form of FFI). The C API follows the same design of the Rust one, modulo the constraints imposed by the C language itself.
When running cargo build
, a static library called libquiche.a
will be
built automatically alongside the Rust one. This is fully stand-alone and can
be linked directly into C/C++ applications.
The first step after cloning the git repo is updating the git submodules:
$ git submodule update --init
You can now build quiche using cargo:
$ cargo build --examples
As well as run its tests:
$ cargo test
Note that BoringSSL, used to implement QUIC's cryptographic handshake based on
TLS, needs to be built and linked to quiche. This is done automatically when
building quiche using cargo, but requires the cmake
and go
commands to be
available during the build process.
In alternative you can use your own custom build of BoringSSL by configuring
the BoringSSL directory with the QUICHE_BSSL_PATH
environment variable:
$ QUICHE_BSSL_PATH="/path/to/boringssl" cargo build --examples
Copyright (C) 2018, Cloudflare, Inc.
Copyright (C) 2018, Alessandro Ghedini
See COPYING for the license.