Skip to content

Commit

Permalink
Upgrade quinn (#26)
Browse files Browse the repository at this point in the history
* Initial upgrade to quinn 0.11

* Fix the examples.

* Bumpers
  • Loading branch information
kixelated authored May 8, 2024
1 parent 48b7c62 commit 85e3c0e
Show file tree
Hide file tree
Showing 10 changed files with 758 additions and 364 deletions.
960 changes: 679 additions & 281 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions web-transport-proto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors = ["Luke Curley"]
repository = "https://github.com/kixelated/web-transport-rs"
license = "MIT"

version = "0.1.1"
version = "0.2.0"
edition = "2021"

keywords = ["quic", "http3", "webtransport"]
Expand All @@ -14,7 +14,7 @@ categories = ["network-programming", "web-programming"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
http = "0.2"
http = "1"
bytes = "1"
thiserror = "1"
url = "^2.4.0"
17 changes: 10 additions & 7 deletions web-transport-quinn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors = ["Luke Curley"]
repository = "https://github.com/kixelated/web-transport-rs"
license = "MIT"

version = "0.2.1"
version = "0.3.0"
edition = "2021"

keywords = ["quic", "http3", "webtransport"]
Expand All @@ -14,12 +14,13 @@ categories = ["network-programming", "web-programming"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
web-transport-proto = { path = "../web-transport-proto", version = "0.1.1" }
web-transport-proto = { path = "../web-transport-proto", version = "0.2" }

quinn = "0.11"
quinn-proto = "0.11"

quinn = "0.10"
bytes = "1"
quinn-proto = "0.10"
http = "0.2"
http = "1"
thiserror = "1"
futures = "0.3"
url = "2"
Expand All @@ -31,7 +32,9 @@ tokio = { version = "1", default-features = false }
[dev-dependencies]
anyhow = "1"
tokio = { version = "1", features = ["full"] }
rustls = { version = "0.21", features = ["dangerous_configuration", "quic"] }
env_logger = "0.10"
clap = { version = "4", features = ["derive"] }
rustls-pemfile = "1.0.2"
rustls-pemfile = "2"
rustls = { version = "0.23", features = ["ring"] }
quinn = { version = "0.11", features = ["ring"] }
quinn-proto = { version = "0.11", features = ["ring"] }
38 changes: 19 additions & 19 deletions web-transport-quinn/examples/echo-client.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::{fs, io, path};
use std::{fs, io, path, sync::Arc};

use anyhow::Context;
use clap::Parser;
use rustls::Certificate;
use rustls::pki_types::CertificateDer;
use url::Url;

#[derive(Parser, Debug)]
Expand All @@ -28,29 +28,29 @@ async fn main() -> anyhow::Result<()> {
let chain = fs::File::open(args.tls_cert).context("failed to open cert file")?;
let mut chain = io::BufReader::new(chain);

let chain: Vec<Certificate> = rustls_pemfile::certs(&mut chain)?
.into_iter()
.map(Certificate)
.collect();
let chain: Vec<CertificateDer> = rustls_pemfile::certs(&mut chain)
.collect::<Result<_, _>>()
.context("failed to load certs")?;

anyhow::ensure!(!chain.is_empty(), "could not find certificate");

let mut roots = rustls::RootCertStore::empty();
roots.add(&chain[0])?;
roots.add_parsable_certificates(chain);

// Standard quinn setup, accepting only the given certificate.
// You should use system roots in production.
let mut tls_config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(roots)
.with_no_client_auth();

tls_config.alpn_protocols = vec![web_transport_quinn::ALPN.to_vec()]; // this one is important

let config = quinn::ClientConfig::new(std::sync::Arc::new(tls_config));

let addr = "[::]:0".parse()?;
let mut client = quinn::Endpoint::client(addr)?;
let mut config = rustls::ClientConfig::builder_with_provider(Arc::new(
rustls::crypto::ring::default_provider(),
))
.with_protocol_versions(&[&rustls::version::TLS13])?
.with_root_certificates(roots)
.with_no_client_auth();
config.alpn_protocols = vec![web_transport_quinn::ALPN.to_vec()]; // this one is important

let config: quinn::crypto::rustls::QuicClientConfig = config.try_into()?;
let config = quinn::ClientConfig::new(Arc::new(config));

let mut client = quinn::Endpoint::client("[::]:0".parse()?)?;
client.set_default_client_config(config);

log::info!("connecting to {}", args.url);
Expand All @@ -71,7 +71,7 @@ async fn main() -> anyhow::Result<()> {
log::info!("sent: {}", msg);

// Shut down the send stream.
send.finish().await?;
send.finish()?;

// Read back the message.
let msg = recv.read_to_end(1024).await?;
Expand Down
48 changes: 19 additions & 29 deletions web-transport-quinn/examples/echo-server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ use std::{
fs,
io::{self, Read},
path,
sync::Arc,
};

use anyhow::Context;

use clap::Parser;
use rustls::Certificate;
use rustls::pki_types::CertificateDer;
use web_transport_quinn::Session;

#[derive(Parser, Debug)]
Expand Down Expand Up @@ -37,10 +38,9 @@ async fn main() -> anyhow::Result<()> {
let chain = fs::File::open(args.tls_cert).context("failed to open cert file")?;
let mut chain = io::BufReader::new(chain);

let chain: Vec<Certificate> = rustls_pemfile::certs(&mut chain)?
.into_iter()
.map(Certificate)
.collect();
let chain: Vec<CertificateDer> = rustls_pemfile::certs(&mut chain)
.collect::<Result<_, _>>()
.context("failed to load certs")?;

anyhow::ensure!(!chain.is_empty(), "could not find certificate");

Expand All @@ -53,33 +53,23 @@ async fn main() -> anyhow::Result<()> {

// Try to parse a PKCS#8 key
// -----BEGIN PRIVATE KEY-----
let mut keys = rustls_pemfile::pkcs8_private_keys(&mut io::Cursor::new(&buf))?;

// Try again but with EC keys this time
// -----BEGIN EC PRIVATE KEY-----
if keys.is_empty() {
keys = rustls_pemfile::ec_private_keys(&mut io::Cursor::new(&buf))?
};

anyhow::ensure!(!keys.is_empty(), "could not find private key");
anyhow::ensure!(keys.len() < 2, "expected a single key");

//let certs = certs.into_iter().map(rustls::Certificate).collect();
let key = rustls::PrivateKey(keys.remove(0));
let key = rustls_pemfile::private_key(&mut io::Cursor::new(&buf))
.context("failed to load private key")?
.context("missing private key")?;

// Standard Quinn setup
let mut tls_config = rustls::ServerConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups()
.with_protocol_versions(&[&rustls::version::TLS13])
.unwrap()
.with_no_client_auth()
.with_single_cert(chain, key)?;
let mut config = rustls::ServerConfig::builder_with_provider(Arc::new(
rustls::crypto::ring::default_provider(),
))
.with_protocol_versions(&[&rustls::version::TLS13])?
.with_no_client_auth()
.with_single_cert(chain, key)?;

tls_config.max_early_data_size = u32::MAX;
tls_config.alpn_protocols = vec![web_transport_quinn::ALPN.to_vec()]; // this one is important
config.max_early_data_size = u32::MAX;
config.alpn_protocols = vec![web_transport_quinn::ALPN.to_vec()]; // this one is important

let config = quinn::ServerConfig::with_crypto(std::sync::Arc::new(tls_config));
let config: quinn::crypto::rustls::QuicServerConfig = config.try_into()?;
let config = quinn::ServerConfig::with_crypto(Arc::new(config));

log::info!("listening on {}", args.addr);

Expand All @@ -100,7 +90,7 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}

async fn run_conn(conn: quinn::Connecting) -> anyhow::Result<()> {
async fn run_conn(conn: quinn::Incoming) -> anyhow::Result<()> {
log::info!("received new QUIC connection");

// Wait for the QUIC handshake to complete.
Expand Down
23 changes: 11 additions & 12 deletions web-transport-quinn/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub enum WriteError {
SessionError(#[from] SessionError),

#[error("stream closed")]
Closed,
ClosedStream,
}

impl From<quinn::WriteError> for WriteError {
Expand All @@ -51,7 +51,7 @@ impl From<quinn::WriteError> for WriteError {
None => WriteError::InvalidStopped(code),
}
}
quinn::WriteError::UnknownStream => WriteError::Closed,
quinn::WriteError::ClosedStream => WriteError::ClosedStream,
quinn::WriteError::ConnectionLost(e) => WriteError::SessionError(e.into()),
quinn::WriteError::ZeroRttRejected => unreachable!("0-RTT not supported"),
}
Expand All @@ -71,7 +71,7 @@ pub enum ReadError {
InvalidReset(quinn::VarInt),

#[error("stream already closed")]
Closed,
ClosedStream,

#[error("ordered read on unordered stream")]
IllegalOrderedRead,
Expand All @@ -88,7 +88,7 @@ impl From<quinn::ReadError> for ReadError {
}
quinn::ReadError::ConnectionLost(e) => ReadError::SessionError(e.into()),
quinn::ReadError::IllegalOrderedRead => ReadError::IllegalOrderedRead,
quinn::ReadError::UnknownStream => ReadError::Closed,
quinn::ReadError::ClosedStream => ReadError::ClosedStream,
quinn::ReadError::ZeroRttRejected => unreachable!("0-RTT not supported"),
}
}
Expand All @@ -98,7 +98,7 @@ impl From<quinn::ReadError> for ReadError {
#[derive(Clone, Error, Debug)]
pub enum ReadExactError {
#[error("finished early")]
FinishedEarly,
FinishedEarly(usize),

#[error("read error: {0}")]
ReadError(#[from] ReadError),
Expand All @@ -107,7 +107,7 @@ pub enum ReadExactError {
impl From<quinn::ReadExactError> for ReadExactError {
fn from(e: quinn::ReadExactError) -> Self {
match e {
quinn::ReadExactError::FinishedEarly => ReadExactError::FinishedEarly,
quinn::ReadExactError::FinishedEarly(size) => ReadExactError::FinishedEarly(size),
quinn::ReadExactError::ReadError(e) => ReadExactError::ReadError(e.into()),
}
}
Expand All @@ -132,14 +132,14 @@ impl From<quinn::ReadToEndError> for ReadToEndError {
}
}

/// An error indicating the stream was already closed. Same as [`quinn::UnknownStream`] but a less confusing name.
/// An error indicating the stream was already closed.
#[derive(Clone, Error, Debug)]
#[error("stream closed")]
pub struct StreamClosed;
pub struct ClosedStream;

impl From<quinn::UnknownStream> for StreamClosed {
fn from(_: quinn::UnknownStream) -> Self {
StreamClosed
impl From<quinn::ClosedStream> for ClosedStream {
fn from(_: quinn::ClosedStream) -> Self {
ClosedStream
}
}

Expand All @@ -157,7 +157,6 @@ impl From<quinn::StoppedError> for StoppedError {
fn from(e: quinn::StoppedError) -> Self {
match e {
quinn::StoppedError::ConnectionLost(e) => StoppedError::SessionError(e.into()),
quinn::StoppedError::UnknownStream => StoppedError::Closed,
quinn::StoppedError::ZeroRttRejected => unreachable!("0-RTT not supported"),
}
}
Expand Down
2 changes: 1 addition & 1 deletion web-transport-quinn/src/recv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl RecvStream {

/// Tell the other end to stop sending data with the given error code. See [`quinn::RecvStream::stop`].
/// This is a u32 with WebTransport since it shares the error space with HTTP/3.
pub fn stop(&mut self, code: u32) -> Result<(), quinn::UnknownStream> {
pub fn stop(&mut self, code: u32) -> Result<(), quinn::ClosedStream> {
let code = web_transport_proto::error_to_http3(code);
let code = quinn::VarInt::try_from(code).unwrap();
self.inner.stop(code)
Expand Down
21 changes: 12 additions & 9 deletions web-transport-quinn/src/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{

use bytes::Bytes;

use crate::{StoppedError, StreamClosed, WriteError};
use crate::{ClosedStream, StoppedError, WriteError};

/// A stream that can be used to send bytes. See [`quinn::SendStream`].
///
Expand All @@ -24,7 +24,7 @@ impl SendStream {

/// Abruptly reset the stream with the provided error code. See [`quinn::SendStream::reset`].
/// This is a u32 with WebTransport because we share the error space with HTTP/3.
pub fn reset(&mut self, code: u32) -> Result<(), StreamClosed> {
pub fn reset(&mut self, code: u32) -> Result<(), ClosedStream> {
let code = web_transport_proto::error_to_http3(code);
let code = quinn::VarInt::try_from(code).unwrap();
self.stream.reset(code).map_err(Into::into)
Expand All @@ -33,8 +33,10 @@ impl SendStream {
/// Wait until the stream has been stopped and return the error code. See [`quinn::SendStream::stopped`].
/// Unlike Quinn, this returns None if the code is not a valid WebTransport error code.
pub async fn stopped(&mut self) -> Result<Option<u32>, StoppedError> {
let code = self.stream.stopped().await?;
Ok(web_transport_proto::error_from_http3(code.into_inner()))
Ok(match self.stream.stopped().await? {
Some(code) => web_transport_proto::error_from_http3(code.into_inner()),
None => None,
})
}

// Unfortunately, we have to wrap WriteError for a bunch of functions.
Expand Down Expand Up @@ -68,15 +70,15 @@ impl SendStream {
}

/// Wait until all of the data has been written to the stream. See [`quinn::SendStream::finish`].
pub async fn finish(&mut self) -> Result<(), WriteError> {
self.stream.finish().await.map_err(Into::into)
pub fn finish(&mut self) -> Result<(), ClosedStream> {
self.stream.finish().map_err(Into::into)
}

pub fn set_priority(&self, order: i32) -> Result<(), StreamClosed> {
pub fn set_priority(&self, order: i32) -> Result<(), ClosedStream> {
self.stream.set_priority(order).map_err(Into::into)
}

pub fn priority(&self) -> Result<i32, StreamClosed> {
pub fn priority(&self) -> Result<i32, ClosedStream> {
self.stream.priority().map_err(Into::into)
}
}
Expand All @@ -87,7 +89,8 @@ impl tokio::io::AsyncWrite for SendStream {
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.stream).poll_write(cx, buf)
// We have to use this syntax because quinn added its own poll_write method.
tokio::io::AsyncWrite::poll_write(Pin::new(&mut self.stream), cx, buf)
}

fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
Expand Down
5 changes: 3 additions & 2 deletions web-transport-quinn/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,9 @@ impl Session {

if let Some(session_id) = self.session_id {
// We have to check and strip the session ID from the datagram.
let actual_id = VarInt::decode(&mut cursor)
.map_err(|_| WebTransportError::ReadError(quinn::ReadExactError::FinishedEarly))?;
let actual_id = VarInt::decode(&mut cursor).map_err(|_| {
WebTransportError::ReadError(quinn::ReadExactError::FinishedEarly(0))
})?;
if actual_id != session_id {
return Err(WebTransportError::UnknownSession.into());
}
Expand Down
4 changes: 2 additions & 2 deletions web-transport/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors = ["Luke Curley"]
repository = "https://github.com/kixelated/web-transport-rs"
license = "MIT"

version = "0.2.1"
version = "0.3.0"
edition = "2021"

keywords = ["quic", "http3", "webtransport"]
Expand All @@ -21,4 +21,4 @@ thiserror = "1"
web-transport-wasm = { version = "0.1", path = "../web-transport-wasm" }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
web-transport-quinn = { version = "0.2.1", path = "../web-transport-quinn" }
web-transport-quinn = { version = "0.3", path = "../web-transport-quinn" }

0 comments on commit 85e3c0e

Please sign in to comment.