This commit is contained in:
Luke Curley 2023-07-18 10:42:38 -07:00
parent a06d273e69
commit f9c3f8b898
14 changed files with 529 additions and 437 deletions

303
Cargo.lock generated
View File

@ -66,6 +66,124 @@ version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "async-channel"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-executor"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
dependencies = [
"async-lock",
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"slab",
]
[[package]]
name = "async-global-executor"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
dependencies = [
"async-channel",
"async-executor",
"async-io",
"async-lock",
"blocking",
"futures-lite",
"once_cell",
]
[[package]]
name = "async-io"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
dependencies = [
"async-lock",
"autocfg",
"cfg-if",
"concurrent-queue",
"futures-lite",
"log",
"parking",
"polling",
"rustix",
"slab",
"socket2 0.4.9",
"waker-fn",
]
[[package]]
name = "async-lock"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
dependencies = [
"event-listener",
]
[[package]]
name = "async-std"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
"async-channel",
"async-global-executor",
"async-io",
"async-lock",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"once_cell",
"pin-project-lite",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]]
name = "async-task"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae"
[[package]]
name = "async-trait"
version = "0.1.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2d0f03b3640e3a630367e40c468cb7f309529c708ed1d88597047b0e7c6ef7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "atomic-waker"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -110,6 +228,21 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "blocking"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65"
dependencies = [
"async-channel",
"async-lock",
"async-task",
"atomic-waker",
"fastrand",
"futures-lite",
"log",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.13.0" version = "3.13.0"
@ -188,6 +321,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "concurrent-queue"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.3" version = "0.9.3"
@ -213,6 +355,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -276,6 +427,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.9.0" version = "1.9.0"
@ -348,6 +505,21 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.28" version = "0.3.28"
@ -410,6 +582,18 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "gloo-timers"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.19" version = "0.3.19"
@ -669,6 +853,15 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.146" version = "0.2.146"
@ -696,6 +889,9 @@ name = "log"
version = "0.4.19" version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
dependencies = [
"value-bag",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
@ -735,6 +931,7 @@ name = "moq-demo"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes",
"clap", "clap",
"env_logger", "env_logger",
"hex", "hex",
@ -747,15 +944,21 @@ dependencies = [
"rustls-pemfile", "rustls-pemfile",
"tokio", "tokio",
"warp", "warp",
"webtransport-generic 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"webtransport-quinn",
] ]
[[package]] [[package]]
name = "moq-transport" name = "moq-transport"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"bytes", "bytes",
"futures",
"log", "log",
"thiserror", "thiserror",
"tokio",
"webtransport-generic 0.2.0",
] ]
[[package]] [[package]]
@ -780,15 +983,15 @@ name = "moq-warp"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes",
"log", "log",
"moq-transport", "moq-transport",
"moq-transport-quinn",
"mp4", "mp4",
"quinn",
"ring", "ring",
"rustls 0.21.2", "rustls 0.21.2",
"rustls-pemfile", "rustls-pemfile",
"tokio", "tokio",
"webtransport-generic 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -888,6 +1091,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "parking"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -949,6 +1158,22 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "polling"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg",
"bitflags",
"cfg-if",
"concurrent-queue",
"libc",
"log",
"pin-project-lite",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.17"
@ -1606,12 +1831,24 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "value-bag"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"
@ -1684,6 +1921,18 @@ dependencies = [
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.87" version = "0.2.87"
@ -1733,6 +1982,56 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "webtransport-generic"
version = "0.2.0"
dependencies = [
"async-trait",
"bytes",
"log",
"thiserror",
]
[[package]]
name = "webtransport-generic"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db80267637ca8d24cd3425a3e993842ed9e128620f5fcd9603145fee8fe808fd"
dependencies = [
"bytes",
"log",
"thiserror",
]
[[package]]
name = "webtransport-proto"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24a891faa138075b2338bc3da6dc6a67c174118a088e2809db6882d410973cd6"
dependencies = [
"bytes",
"http",
"thiserror",
]
[[package]]
name = "webtransport-quinn"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea529a3044e99379b180581e8b0773fa7feb1fc53e341f36927a755ac05abd62"
dependencies = [
"async-std",
"bytes",
"futures",
"http",
"quinn",
"quinn-proto",
"thiserror",
"tokio-util",
"webtransport-generic 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"webtransport-proto",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@ -1,7 +1,2 @@
[workspace] [workspace]
members = [ members = ["moq-transport", "moq-transport-quinn", "moq-demo", "moq-warp"]
"moq-transport",
"moq-transport-quinn",
"moq-demo-quinn",
"moq-warp",
]

View File

@ -1,60 +0,0 @@
use h3::quic::SendStream;
use h3::quic::RecvStream;
use h3::quic::SendStreamUnframed;
pub struct QuinnSendStream {
stream: h3_webtransport::stream::SendStream<h3_quinn::SendStream<bytes::Bytes>, bytes::Bytes>
}
impl QuinnSendStream {
pub fn new(stream: h3_webtransport::stream::SendStream<h3_quinn::SendStream<bytes::Bytes>, bytes::Bytes>) -> QuinnSendStream {
QuinnSendStream { stream }
}
}
impl webtransport_generic::SendStream for QuinnSendStream {
type Error = anyhow::Error;
fn poll_finish(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
self.stream.poll_finish(cx).map_err(|e| anyhow::anyhow!("{:?}", e))
}
fn reset(&mut self, reset_code: u32) {
self.stream.reset(reset_code as u64)
}
fn poll_send<D: bytes::Buf>(
&mut self,
cx: &mut std::task::Context<'_>,
buf: &mut D,
) -> std::task::Poll<Result<usize, anyhow::Error>> {
self.stream.poll_send(cx, buf).map_err(|e| anyhow::anyhow!("{:?}", e))
}
}
pub struct QuinnRecvStream {
stream: h3_webtransport::stream::RecvStream<h3_quinn::RecvStream, bytes::Bytes>
}
impl QuinnRecvStream {
pub fn new(stream: h3_webtransport::stream::RecvStream<h3_quinn::RecvStream, bytes::Bytes>) -> QuinnRecvStream {
QuinnRecvStream { stream }
}
}
impl webtransport_generic::RecvStream for QuinnRecvStream {
type Error = anyhow::Error;
type Buf = bytes::Bytes;
fn poll_data(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<Option<Self::Buf>, Self::Error>> {
self.stream.poll_data(cx).map_err(|e| anyhow::anyhow!("{:?}", e))
}
fn stop_sending(&mut self, error_code: u32) {
self.stream.stop_sending(error_code as u64)
}
}

View File

@ -1,5 +1,5 @@
[package] [package]
name = "moq-demo-quinn" name = "moq-demo"
description = "Media over QUIC" description = "Media over QUIC"
authors = ["Luke Curley"] authors = ["Luke Curley"]
repository = "https://github.com/kixelated/moq-rs" repository = "https://github.com/kixelated/moq-rs"
@ -11,7 +11,6 @@ edition = "2021"
keywords = ["quic", "http3", "webtransport", "media", "live"] keywords = ["quic", "http3", "webtransport", "media", "live"]
categories = ["multimedia", "network-programming", "web-programming"] categories = ["multimedia", "network-programming", "web-programming"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
@ -39,10 +38,8 @@ anyhow = "1.0.70"
bytes = "1" bytes = "1"
webtransport-generic = {git = "https://github.com/kixelated/webtransport-rs"} webtransport-generic = "0.2"
webtransport-quinn = "0.3"
moq-transport = { path = "../moq-transport" } moq-transport = { path = "../moq-transport" }
moq-warp = { path = "../moq-warp" } moq-warp = { path = "../moq-warp" }
h3 = { git = "https://github.com/hyperium/h3", branch = "master" }
h3-quinn = { git = "https://github.com/hyperium/h3", branch = "master" }
h3-webtransport = { git = "https://github.com/hyperium/h3", branch = "master" }

View File

@ -7,7 +7,10 @@ use ring::digest::{digest, SHA256};
use tokio::task::JoinSet; use tokio::task::JoinSet;
use warp::Filter; use warp::Filter;
use moq_warp::{relay::{self, broker::Broadcasts}, source}; use moq_warp::{
relay::{self, broker::Broadcasts},
source,
};
mod server; mod server;
@ -29,10 +32,8 @@ struct Cli {
/// Use the media file at this path /// Use the media file at this path
#[arg(short, long, default_value = "media/fragmented.mp4")] #[arg(short, long, default_value = "media/fragmented.mp4")]
media: path::PathBuf, media: path::PathBuf,
} }
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
env_logger::init(); env_logger::init();
@ -63,12 +64,11 @@ async fn main() -> anyhow::Result<()> {
res = media.run() => res.context("failed to run media source"), res = media.run() => res.context("failed to run media source"),
res = serve => res.context("failed to run HTTP server"), res = serve => res.context("failed to run HTTP server"),
} }
} }
async fn run_server(config: relay::ServerConfig, broker: Broadcasts) -> anyhow::Result<()> { async fn run_server(config: relay::ServerConfig, broker: Broadcasts) -> anyhow::Result<()> {
let quinn = server::Server::new(config).unwrap();
let quinn = server::Server::new_quinn_connection(config).unwrap();
let mut tasks = JoinSet::new(); let mut tasks = JoinSet::new();
loop { loop {
let broker = broker.clone(); let broker = broker.clone();

View File

@ -7,20 +7,16 @@ use h3_webtransport::server::AcceptedBi;
use moq_transport::AcceptSetup; use moq_transport::AcceptSetup;
use moq_warp::relay::ServerConfig; use moq_warp::relay::ServerConfig;
use tokio::task::JoinSet; use tokio::task::JoinSet;
use warp::{Future, http}; use warp::{http, Future};
use self::stream::{QuinnSendStream, QuinnRecvStream};
mod stream;
pub struct Server { pub struct Server {
// The MoQ transport server. // The MoQ transport server.
server: h3_webtransport::server::WebTransportSession<h3_quinn::Connection, bytes::Bytes>, server: quinn::Endpoint,
} }
impl Server { impl Server {
// Create a new server // Create a new server
pub fn new_quinn_connection(config: ServerConfig) -> anyhow::Result<h3_quinn::Endpoint> { pub fn new(config: ServerConfig) -> anyhow::Result<Self> {
// Read the PEM certificate chain // Read the PEM certificate chain
let certs = fs::File::open(config.cert).context("failed to open cert file")?; let certs = fs::File::open(config.cert).context("failed to open cert file")?;
let mut certs = io::BufReader::new(certs); let mut certs = io::BufReader::new(certs);
@ -46,13 +42,7 @@ impl Server {
.with_single_cert(certs, key)?; .with_single_cert(certs, key)?;
tls_config.max_early_data_size = u32::MAX; tls_config.max_early_data_size = u32::MAX;
let alpn: Vec<Vec<u8>> = vec![ let alpn: Vec<Vec<u8>> = vec![webtransport_quinn::ALPN];
b"h3".to_vec(),
b"h3-32".to_vec(),
b"h3-31".to_vec(),
b"h3-30".to_vec(),
b"h3-29".to_vec(),
];
tls_config.alpn_protocols = alpn; tls_config.alpn_protocols = alpn;
let mut server_config = quinn::ServerConfig::with_crypto(std::sync::Arc::new(tls_config)); let mut server_config = quinn::ServerConfig::with_crypto(std::sync::Arc::new(tls_config));
@ -66,7 +56,7 @@ impl Server {
server_config.transport = std::sync::Arc::new(transport_config); server_config.transport = std::sync::Arc::new(transport_config);
let server = quinn::Endpoint::server(server_config, config.addr)?; let server = quinn::Endpoint::server(server_config, config.addr)?;
Ok(server) Ok(Self { server })
} }
pub async fn accept_new_webtransport_session(endpoint: &h3_quinn::Endpoint) -> anyhow::Result<Connect> { pub async fn accept_new_webtransport_session(endpoint: &h3_quinn::Endpoint) -> anyhow::Result<Connect> {
@ -119,7 +109,6 @@ impl Server {
) )
} }
} }
} }
// The WebTransport CONNECT has arrived, and we need to decide if we accept it. // The WebTransport CONNECT has arrived, and we need to decide if we accept it.
@ -132,7 +121,6 @@ pub struct Connect {
} }
impl Connect { impl Connect {
// Accept the WebTransport session. // Accept the WebTransport session.
pub async fn accept(self) -> anyhow::Result<AcceptSetup<Server>> { pub async fn accept(self) -> anyhow::Result<AcceptSetup<Server>> {
let session = h3_webtransport::server::WebTransportSession::accept(self.req, self.stream, self.conn).await?; let session = h3_webtransport::server::WebTransportSession::accept(self.req, self.stream, self.conn).await?;
@ -143,7 +131,7 @@ impl Connect {
.context("failed to accept bidi stream")? .context("failed to accept bidi stream")?
.unwrap(); .unwrap();
Ok(moq_transport::Session::accept(Box::new(control_stream_send), Box::new(control_stream_recv), Box::new(session)).await?) Ok(moq_transport::Session::accept(session).await?)
} }
// Reject the WebTransport session with a HTTP response. // Reject the WebTransport session with a HTTP response.
@ -153,9 +141,7 @@ impl Connect {
} }
} }
impl webtransport_generic::Connection for Server { impl webtransport_generic::Connection for Server {
type Error = anyhow::Error; type Error = anyhow::Error;
type SendStream = QuinnSendStream; type SendStream = QuinnSendStream;
@ -180,7 +166,9 @@ impl webtransport_generic::Connection for Server {
let fut = std::pin::pin!(fut); let fut = std::pin::pin!(fut);
let res = std::task::ready!(fut.poll(cx).map_err(|e| anyhow::anyhow!("{:?}", e))); let res = std::task::ready!(fut.poll(cx).map_err(|e| anyhow::anyhow!("{:?}", e)));
match res { match res {
Ok(Some(AcceptedBi::Request(_, _))) => std::task::Poll::Ready(Err(anyhow::anyhow!("received new session whils accepting bidi stream"))), Ok(Some(AcceptedBi::Request(_, _))) => {
std::task::Poll::Ready(Err(anyhow::anyhow!("received new session whils accepting bidi stream")))
}
Ok(Some(AcceptedBi::BidiStream(_, s))) => { Ok(Some(AcceptedBi::BidiStream(_, s))) => {
let (send, recv) = s.split(); let (send, recv) = s.split();
std::task::Poll::Ready(Ok(Some((QuinnSendStream::new(send), QuinnRecvStream::new(recv))))) std::task::Poll::Ready(Ok(Some((QuinnSendStream::new(send), QuinnRecvStream::new(recv)))))
@ -200,8 +188,7 @@ impl webtransport_generic::Connection for Server {
.map_ok(|s| { .map_ok(|s| {
let (send, recv) = s.split(); let (send, recv) = s.split();
(QuinnSendStream::new(send), QuinnRecvStream::new(recv)) (QuinnSendStream::new(send), QuinnRecvStream::new(recv))
} })
)
.map_err(|e| anyhow::anyhow!("{:?}", e)) .map_err(|e| anyhow::anyhow!("{:?}", e))
} }

View File

@ -20,5 +20,5 @@ thiserror = "1.0.21"
log = "0.4" log = "0.4"
tokio = { version = "1.27", features = ["full"] } tokio = { version = "1.27", features = ["full"] }
anyhow = "1.0.70" anyhow = "1.0.70"
webtransport-generic = { version = "0.2", path = "../../webtransport-rs/webtransport-generic" }
webtransport-generic = {git = "https://github.com/kixelated/webtransport-rs"} futures = "0.3"

View File

@ -1,55 +1,25 @@
use webtransport_generic::{RecvStream, SendStream};
use crate::{Decode, DecodeError, Encode, Message}; use crate::{Decode, DecodeError, Encode, Message};
use crate::network::stream::{recv, send}; use webtransport_generic::{RecvStream, SendStream};
use bytes::{Buf, BytesMut}; use bytes::{Buf, BytesMut};
use std::future;
use std::io::Cursor; use std::io::Cursor;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use anyhow::Context; use anyhow::Context;
pub struct SendControl<S: SendStream> {
pub struct Control<S: SendStream, R: RecvStream> { stream: S,
sender: ControlSend<S>,
recver: ControlRecv<R>,
}
impl<S: SendStream, R: RecvStream> Control<S, R>{
pub(crate) fn new(sender: Box<S>, recver: Box<R>) -> Self {
let sender = ControlSend::new(sender);
let recver = ControlRecv::new(recver);
Self { sender, recver }
}
pub fn split(self) -> (ControlSend<S>, ControlRecv<R>) {
(self.sender, self.recver)
}
pub async fn send<T: Into<Message>>(&mut self, msg: T) -> anyhow::Result<()> {
self.sender.send(msg)
.await
.map_err(|e| anyhow::anyhow!("{:?}", e))
.context("error sending control message")
}
pub async fn recv(&mut self) -> anyhow::Result<Message> {
self.recver.recv().await
}
}
pub struct ControlSend<S: SendStream> {
stream: Box<S>,
buf: BytesMut, // reuse a buffer to encode messages. buf: BytesMut, // reuse a buffer to encode messages.
} }
impl<S: SendStream> ControlSend<S> { impl<S: SendStream> SendControl<S> {
pub fn new(inner: Box<S>) -> Self { pub fn new(stream: S) -> Self {
Self { Self {
buf: BytesMut::new(), buf: BytesMut::new(),
stream: inner, stream,
} }
} }
@ -61,16 +31,17 @@ impl<S: SendStream> ControlSend<S> {
msg.encode(&mut self.buf)?; msg.encode(&mut self.buf)?;
// TODO make this work with select! // TODO make this work with select!
send(self.stream.as_mut(), &mut self.buf) self.stream
.send(&mut self.buf)
.await .await
.map_err(|e| anyhow::anyhow!("{:?}", e.into()))
.context("error sending control message")?; .context("error sending control message")?;
Ok(()) Ok(())
} }
// Helper that lets multiple threads send control messages. // Helper that lets multiple threads send control messages.
pub fn share(self) -> ControlShared<S> { pub fn share(self) -> SharedControl<S> {
ControlShared { SharedControl {
stream: Arc::new(Mutex::new(self)), stream: Arc::new(Mutex::new(self)),
} }
} }
@ -79,27 +50,27 @@ impl<S: SendStream> ControlSend<S> {
// Helper that allows multiple threads to send control messages. // Helper that allows multiple threads to send control messages.
// There's no equivalent for receiving since only one thread should be receiving at a time. // There's no equivalent for receiving since only one thread should be receiving at a time.
#[derive(Clone)] #[derive(Clone)]
pub struct ControlShared<S: SendStream> { pub struct SharedControl<S: SendStream> {
stream: Arc<Mutex<ControlSend<S>>>, stream: Arc<Mutex<SendControl<S>>>,
} }
impl<S: SendStream> ControlShared<S> { impl<S: SendStream> SharedControl<S> {
pub async fn send<T: Into<Message>>(&mut self, msg: T) -> anyhow::Result<()> { pub async fn send<T: Into<Message>>(&mut self, msg: T) -> anyhow::Result<()> {
let mut stream = self.stream.lock().await; let mut stream = self.stream.lock().await;
stream.send(msg).await stream.send(msg).await
} }
} }
pub struct ControlRecv<R: RecvStream> { pub struct RecvControl<R: RecvStream> {
stream: Box<R>, stream: R,
buf: BytesMut, // data we've read but haven't fully decoded yet buf: BytesMut, // data we've read but haven't fully decoded yet
} }
impl<R: RecvStream> ControlRecv<R> { impl<R: RecvStream> RecvControl<R> {
pub fn new(inner: Box<R>) -> Self { pub fn new(stream: R) -> Self {
Self { Self {
buf: BytesMut::new(), buf: BytesMut::new(),
stream: inner, stream,
} }
} }
@ -119,9 +90,8 @@ impl<R: RecvStream> ControlRecv<R> {
} }
Err(DecodeError::UnexpectedEnd) => { Err(DecodeError::UnexpectedEnd) => {
// The decode failed, so we need to append more data. // The decode failed, so we need to append more data.
recv(self.stream.as_mut(), &mut self.buf) future::poll_fn(|cx| self.stream.poll_recv(cx, &mut self.buf))
.await .await
.map_err(|e| anyhow::anyhow!("{:?}", e.into()))
.context("error receiving control message")?; .context("error receiving control message")?;
} }
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),

View File

@ -1,13 +1,7 @@
mod stream;
mod control; mod control;
mod object; mod objects;
mod server; mod server;
use std::sync::Arc;
use std::sync::Mutex;
pub type SharedConnection<C> = Arc<Mutex<Box<C>>>;
pub use stream::*;
pub use control::*; pub use control::*;
pub use object::*; pub use objects::*;
pub use server::*; pub use server::*;

View File

@ -1,142 +0,0 @@
use anyhow::Context;
use bytes::{Buf, BytesMut};
use webtransport_generic::{Connection, SendStream, RecvStream};
use crate::{Decode, DecodeError, Encode, Object};
use std::{io::Cursor, marker::PhantomData};
use crate::network::SharedConnection;
use super::stream::{open_uni_shared, send, recv, accept_uni_shared};
// TODO support clients
pub struct Objects<C: Connection> {
send: SendObjects<C>,
recv: RecvObjects<C>,
}
impl<S: SendStream, R: RecvStream + 'static, C: Connection<SendStream = S, RecvStream = R> + Send> Objects<C> {
pub fn new(session: SharedConnection<C>) -> Self {
let send = SendObjects::new(session.clone());
let recv = RecvObjects::new(session);
Self { send, recv }
}
pub fn split(self) -> (SendObjects<C>, RecvObjects<C>) {
(self.send, self.recv)
}
pub async fn recv(&mut self) -> anyhow::Result<(Object, R)> {
self.recv.recv().await
}
pub async fn send(&mut self, header: Object) -> anyhow::Result<C::SendStream> {
self.send.send(header).await
}
}
pub struct SendObjects<C: Connection> {
session: SharedConnection<C>,
// A reusable buffer for encoding messages.
buf: BytesMut,
_marker: PhantomData<C>,
}
impl<S: SendStream, C: Connection<SendStream = S>> SendObjects<C> {
pub fn new(session: SharedConnection<C>) -> Self {
Self {
session,
buf: BytesMut::new(),
_marker: PhantomData,
}
}
pub async fn send(&mut self, header: Object) -> anyhow::Result<C::SendStream> {
self.buf.clear();
header.encode(&mut self.buf).unwrap();
// TODO support select! without making a new stream.
let mut stream = open_uni_shared(self.session.clone())
.await
.map_err(|e| anyhow::anyhow!("{:?}", e.into()))
.context("failed to open uni stream")?;
send(&mut stream, &mut self.buf)
.await
.map_err(|e| anyhow::anyhow!("{:?}", e.into()))
.context("failed to send data on stream")?;
Ok(stream)
}
}
impl<S: SendStream, C: Connection<SendStream = S>> Clone for SendObjects<C> {
fn clone(&self) -> Self {
Self {
session: self.session.clone(),
buf: BytesMut::new(),
_marker: PhantomData,
}
}
}
// Not clone, so we don't accidentally have two listners.
pub struct RecvObjects<C: Connection> {
session: SharedConnection<C>,
// A uni stream that's been accepted but not fully read from yet.
stream: Option<Box<C::RecvStream>>,
// Data that we've read but haven't formed a full message yet.
buf: BytesMut,
}
impl<R: RecvStream + 'static, C: Connection<RecvStream = R>> RecvObjects<C> {
pub fn new(session: SharedConnection<C>) -> Self {
Self {
session,
stream: None,
buf: BytesMut::new(),
}
}
pub async fn recv(&mut self) -> anyhow::Result<(Object, R)> {
// Make sure any state is saved across await boundaries so this works with select!
let stream = match self.stream.as_mut() {
Some(stream) => stream,
None => {
let stream = accept_uni_shared(self.session.clone())
.await
.map_err(|e| anyhow::anyhow!("{:?}", e.into()))
.context("failed to accept uni stream")?
.context("no uni stream")?;
self.stream.insert(Box::new(stream))
}
};
loop {
// Read the contents of the buffer
let mut peek = Cursor::new(&self.buf);
match Object::decode(&mut peek) {
Ok(header) => {
let stream = self.stream.take().unwrap();
self.buf.advance(peek.position() as usize);
return Ok((header, *stream));
}
Err(DecodeError::UnexpectedEnd) => {
// The decode failed, so we need to append more data.
recv(stream.as_mut(), &mut self.buf)
.await
.map_err(|e| anyhow::anyhow!("{:?}", e.into()))
.context("failed to recv data on stream")?;
}
Err(e) => return Err(e.into()),
}
}
}
}

View File

@ -0,0 +1,103 @@
use crate::{Decode, DecodeError, Encode, Object};
use anyhow::Context;
use bytes::{Buf, BytesMut};
use futures::StreamExt;
use futures::TryStreamExt;
use std::{
io::Cursor,
pin::Pin,
task::{ready, Poll},
};
use tokio::task::JoinSet;
use webtransport_generic::{AsyncRecvStream, AsyncSendStream, AsyncSession};
// TODO support clients
pub struct SendObjects<S: AsyncSession> {
session: S,
// A reusable buffer for encoding messages.
buf: BytesMut,
}
impl<S: AsyncSession> SendObjects<S> {
pub fn new(session: S) -> Self {
Self {
session,
buf: BytesMut::new(),
}
}
pub async fn send(&mut self, header: Object) -> anyhow::Result<S::SendStream> {
self.buf.clear();
header.encode(&mut self.buf).unwrap();
let mut stream = self.session.open_uni().await.context("failed to open uni stream")?;
stream
.send(&mut self.buf)
.await
.context("failed to send data on stream")?;
Ok(stream)
}
}
pub struct RecvObjects<S: AsyncSession> {
session: S,
objects: JoinSet<anyhow::Result<(Object, S::RecvStream)>>,
}
impl<S: AsyncSession + Clone> RecvObjects<S> {
pub fn new(session: S) -> Self {
let streams = futures::stream::unfold(session.clone(), |mut session| async move {
match session.accept_uni().await {
Ok(stream) => Some((stream, session)),
Err(e) => None,
}
});
let objects = streams.map(|mut stream| async move {});
// Decode the object header for up to 16 streams in parallel.
// Otherwise, a lost packet for the first chunk of a stream would block future streams.
let objects = objects.buffer_unordered(16);
let objects = Box::pin(objects);
//try_buffer_unordered
let objects = JoinSet::new();
Self { session, objects }
}
pub async fn next(&mut self) -> anyhow::Result<(Object, S::RecvStream)> {
loop {
tokio::select! {
res = self.objects.join_next(), if !self.objects.is_empty() => {
return res.unwrap()?;
},
res = self.session.accept_uni() => {
let stream = res?;
self.objects.spawn(async move { Self::fetch(stream).await });
},
}
}
}
async fn fetch(mut stream: S::RecvStream) -> anyhow::Result<(Object, S::RecvStream)> {
let mut buf = Vec::with_capacity(64);
loop {
stream.recv(&mut buf).await?.context("no more data")?;
let mut peek = Cursor::new(&buf);
match Object::decode(&mut peek) {
Ok(header) => return Ok((header, stream)),
Err(DecodeError::UnexpectedEnd) => continue,
Err(err) => return Err(err.into()),
}
}
}
}

View File

@ -1,41 +1,48 @@
/*
use anyhow::Context;
use webtransport_generic::{Connection, RecvStream};
use crate::{Message, SetupClient, SetupServer}; use crate::{Message, SetupClient, SetupServer};
use anyhow::Context;
use webtransport_generic::Session as Generic;
use super::{RecvControl, RecvObjects, SendControl, SendObjects};
use super::{Control, Objects}; pub struct Session<S: Generic> {
pub struct Session<C: Connection + Send> { pub send_control: SendControl<S::SendStream>,
pub control: Control<C::SendStream, C::RecvStream>, pub recv_control: RecvControl<S::RecvStream>,
pub objects: Objects<C>, pub send_objects: SendObjects<S>,
pub recv_objects: RecvObjects<S>,
} }
impl<R: RecvStream + 'static, C: Connection<RecvStream = R> + Send> Session<C> { impl<S: Generic + Send> Session<S> {
pub async fn accept(
session: S,
) -> anyhow::Result<AcceptSetup<S>> {
let send_objects = SendObjects::new(session.clone());
let recv_objects = RecvObjects::new(session.clone());
pub async fn accept(control_stream_send: Box<C::SendStream>, control_stream_recv: Box::<C::RecvStream>, connection: Box<C>) -> anyhow::Result<AcceptSetup<C>> { let setup_client = match recv_control await.context("failed to read SETUP")? {
let mut control = Control::new(control_stream_send, control_stream_recv);
let objects = Objects::new(std::sync::Arc::new(std::sync::Mutex::new(connection)));
let setup_client = match control.recv().await.context("failed to read SETUP")? {
Message::SetupClient(setup) => setup, Message::SetupClient(setup) => setup,
_ => anyhow::bail!("expected CLIENT SETUP"), _ => anyhow::bail!("expected CLIENT SETUP"),
}; };
Ok(AcceptSetup { setup_client, control, objects })
Ok(AcceptSetup {
setup_client,
control,
objects,
})
} }
pub fn split(self) -> (Control<C::SendStream, C::RecvStream>, Objects<C>) {
pub fn split(self) -> (Control<C>, Objects<C>) {
(self.control, self.objects) (self.control, self.objects)
} }
} }
pub struct AcceptSetup<C: Generic + Send> {
pub struct AcceptSetup<C: Connection + Send> {
setup_client: SetupClient, setup_client: SetupClient,
control: Control<C::SendStream, C::RecvStream>, control: Control<C>,
objects: Objects<C>, objects: Objects<C>,
} }
impl<C: Connection + Send> AcceptSetup<C> { impl<C: Generic + Send> AcceptSetup<C> {
// Return the setup message we received. // Return the setup message we received.
pub fn setup(&self) -> &SetupClient { pub fn setup(&self) -> &SetupClient {
&self.setup_client &self.setup_client
@ -55,3 +62,5 @@ impl<C: Connection + Send> AcceptSetup<C> {
Ok(()) Ok(())
} }
} }
*/

View File

@ -1,60 +0,0 @@
use std::sync::Arc;
use bytes::{Buf, BytesMut, BufMut};
use webtransport_generic::{Connection, RecvStream, SendStream};
pub async fn accept_uni<C: Connection>(conn: &mut C) -> Result<Option<C::RecvStream>, C::Error> {
std::future::poll_fn(|cx| conn.poll_accept_uni(cx)).await
}
pub async fn accept_uni_shared<C: Connection>(conn: Arc<std::sync::Mutex<Box<C>>>) -> Result<Option<C::RecvStream>, C::Error> {
Ok(std::future::poll_fn(|cx| conn.lock().unwrap().poll_accept_uni(cx)).await?)
}
pub async fn accept_bidi<C: Connection>(conn: &mut C) -> Result<Option<(C::SendStream, C::RecvStream)>, C::Error> {
std::future::poll_fn(|cx| conn.poll_accept_bidi(cx)).await
}
pub async fn accept_bidi_shared<C: Connection>(conn: Arc<std::sync::Mutex<Box<C>>>) -> Result<Option<(C::SendStream, C::RecvStream)>, C::Error> {
std::future::poll_fn(|cx| conn.lock().unwrap().poll_accept_bidi(cx)).await
}
pub async fn open_uni<C: Connection>(conn: &mut C) -> anyhow::Result<C::SendStream, C::Error> {
std::future::poll_fn(|cx| conn.poll_open_uni(cx)).await
}
pub async fn open_uni_shared<C: Connection>(conn: Arc<std::sync::Mutex<Box<C>>>) -> Result<C::SendStream, C::Error> {
std::future::poll_fn(|cx| conn.lock().unwrap().poll_open_uni(cx)).await
}
pub async fn open_bidi<C: Connection>(conn: &mut C) -> anyhow::Result<(C::SendStream, C::RecvStream), C::Error> {
std::future::poll_fn(|cx| conn.poll_open_bidi(cx)).await
}
pub async fn open_bidi_shared<C: Connection>(conn: Arc<std::sync::Mutex<Box<C>>>) -> Result<(C::SendStream, C::RecvStream), C::Error> {
std::future::poll_fn(|cx| conn.lock().unwrap().poll_open_bidi(cx)).await
}
pub async fn recv<R: RecvStream>(recv: &mut R , outbuf: &mut BytesMut) -> Result<bool, R::Error> {
let buf = std::future::poll_fn(|cx| recv.poll_data(cx)).await?;
match buf {
Some(buf) => {
outbuf.put(buf);
Ok(true)
}
None => Ok(false) // stream finished
}
}
pub async fn send<B: Buf, S: SendStream>(send: &mut S, buf: &mut B) -> Result<usize, S::Error> {
std::future::poll_fn(|cx| send.poll_send(cx, buf)).await
}

View File

@ -16,7 +16,7 @@ categories = [ "multimedia", "network-programming", "web-programming" ]
[dependencies] [dependencies]
moq-transport = { path = "../moq-transport" } moq-transport = { path = "../moq-transport" }
webtransport-generic = {git = "https://github.com/kixelated/webtransport-rs"} webtransport-generic = "0.2"
bytes = "1" bytes = "1"
tokio = "1.27" tokio = "1.27"