From d7872ef77d3c1500dc58180fe114675412e6e9a0 Mon Sep 17 00:00:00 2001 From: kixelated Date: Fri, 16 Jun 2023 11:38:19 -0700 Subject: [PATCH] Implement (forked) moq-transport-00 (#34) Not backwards compatible. JS side: https://github.com/kixelated/moq-js/pull/14 --- Cargo.lock | 376 ++-- Cargo.toml | 45 +- moq-transport/Cargo.toml | 37 + moq-transport/src/coding/decode.rs | 54 + moq-transport/src/coding/duration.rs | 24 + moq-transport/src/coding/encode.rs | 58 + moq-transport/src/coding/mod.rs | 9 + moq-transport/src/coding/varint.rs | 139 ++ moq-transport/src/control/announce.rs | 26 + moq-transport/src/control/announce_error.rs | 43 + moq-transport/src/control/announce_ok.rs | 26 + moq-transport/src/control/go_away.rs | 24 + moq-transport/src/control/mod.rs | 110 ++ moq-transport/src/control/stream.rs | 61 + moq-transport/src/control/subscribe.rs | 43 + moq-transport/src/control/subscribe_error.rs | 40 + moq-transport/src/control/subscribe_ok.rs | 39 + moq-transport/src/data/header.rs | 56 + moq-transport/src/data/mod.rs | 5 + moq-transport/src/data/transport.rs | 49 + moq-transport/src/lib.rs | 5 + moq-transport/src/server/endpoint.rs | 42 + moq-transport/src/server/handshake.rs | 114 ++ moq-transport/src/server/mod.rs | 6 + moq-transport/src/server/setup.rs | 42 + moq-transport/src/setup/client.rs | 54 + moq-transport/src/setup/mod.rs | 15 + moq-transport/src/setup/role.rs | 65 + moq-transport/src/setup/server.rs | 44 + moq-transport/src/setup/version.rs | 77 + moq-warp/Cargo.lock | 1809 ++++++++++++++++++ moq-warp/Cargo.toml | 48 + moq-warp/src/app/mod.rs | 5 + {src => moq-warp/src}/app/server.rs | 88 +- moq-warp/src/app/session.rs | 225 +++ {src => moq-warp/src}/lib.rs | 0 {src => moq-warp/src}/main.rs | 10 +- {src => moq-warp/src}/media/mod.rs | 0 {src => moq-warp/src}/media/model.rs | 18 +- {src => moq-warp/src}/media/source.rs | 38 +- {src => moq-warp/src}/media/watch.rs | 2 +- src/app/message.rs | 34 - src/app/mod.rs | 8 - src/app/session.rs | 115 -- 44 files changed, 3673 insertions(+), 455 deletions(-) create mode 100644 moq-transport/Cargo.toml create mode 100644 moq-transport/src/coding/decode.rs create mode 100644 moq-transport/src/coding/duration.rs create mode 100644 moq-transport/src/coding/encode.rs create mode 100644 moq-transport/src/coding/mod.rs create mode 100644 moq-transport/src/coding/varint.rs create mode 100644 moq-transport/src/control/announce.rs create mode 100644 moq-transport/src/control/announce_error.rs create mode 100644 moq-transport/src/control/announce_ok.rs create mode 100644 moq-transport/src/control/go_away.rs create mode 100644 moq-transport/src/control/mod.rs create mode 100644 moq-transport/src/control/stream.rs create mode 100644 moq-transport/src/control/subscribe.rs create mode 100644 moq-transport/src/control/subscribe_error.rs create mode 100644 moq-transport/src/control/subscribe_ok.rs create mode 100644 moq-transport/src/data/header.rs create mode 100644 moq-transport/src/data/mod.rs create mode 100644 moq-transport/src/data/transport.rs create mode 100644 moq-transport/src/lib.rs create mode 100644 moq-transport/src/server/endpoint.rs create mode 100644 moq-transport/src/server/handshake.rs create mode 100644 moq-transport/src/server/mod.rs create mode 100644 moq-transport/src/server/setup.rs create mode 100644 moq-transport/src/setup/client.rs create mode 100644 moq-transport/src/setup/mod.rs create mode 100644 moq-transport/src/setup/role.rs create mode 100644 moq-transport/src/setup/server.rs create mode 100644 moq-transport/src/setup/version.rs create mode 100644 moq-warp/Cargo.lock create mode 100644 moq-warp/Cargo.toml create mode 100644 moq-warp/src/app/mod.rs rename {src => moq-warp/src}/app/server.rs (55%) create mode 100644 moq-warp/src/app/session.rs rename {src => moq-warp/src}/lib.rs (100%) rename {src => moq-warp/src}/main.rs (92%) rename {src => moq-warp/src}/media/mod.rs (100%) rename {src => moq-warp/src}/media/model.rs (57%) rename {src => moq-warp/src}/media/source.rs (94%) rename {src => moq-warp/src}/media/watch.rs (98%) delete mode 100644 src/app/message.rs delete mode 100644 src/app/mod.rs delete mode 100644 src/app/session.rs diff --git a/Cargo.lock b/Cargo.lock index 9803bf8..d1c2e2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -47,7 +47,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -57,7 +57,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -66,6 +66,17 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -91,9 +102,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1e31e207a6b8fb791a38ea3105e6cb541f55e4d029902d3039a4ad07cc4105" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -142,9 +153,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.3.0" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" +checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" dependencies = [ "clap_builder", "clap_derive", @@ -153,9 +164,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.0" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" +checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" dependencies = [ "anstream", "anstyle", @@ -166,9 +177,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck", "proc-macro2", @@ -190,9 +201,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" dependencies = [ "libc", ] @@ -247,7 +258,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -277,9 +288,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -385,9 +396,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -416,7 +427,21 @@ dependencies = [ [[package]] name = "h3" version = "0.0.2" -source = "git+https://github.com/security-union/h3?branch=add-webtransport#db5c723f653911a476bfd8ffcfebf0f8f2eb980d" +source = "git+https://github.com/security-union/h3?branch=add-webtransport#fa956e0d44e66c04545741908fcb3690b0890be6" +dependencies = [ + "bytes", + "fastrand", + "futures-util", + "http", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "h3" +version = "0.0.2" +source = "git+https://github.com/hyperium/h3?branch=master#3ef7c1a37b635e8446322d8f8d3a68580a208ad8" dependencies = [ "bytes", "fastrand", @@ -430,11 +455,25 @@ dependencies = [ [[package]] name = "h3-quinn" version = "0.0.2" -source = "git+https://github.com/security-union/h3?branch=add-webtransport#db5c723f653911a476bfd8ffcfebf0f8f2eb980d" +source = "git+https://github.com/security-union/h3?branch=add-webtransport#fa956e0d44e66c04545741908fcb3690b0890be6" dependencies = [ "bytes", "futures", - "h3", + "h3 0.0.2 (git+https://github.com/security-union/h3?branch=add-webtransport)", + "quinn", + "quinn-proto", + "tokio", + "tokio-util", +] + +[[package]] +name = "h3-quinn" +version = "0.0.3" +source = "git+https://github.com/hyperium/h3?branch=master#3ef7c1a37b635e8446322d8f8d3a68580a208ad8" +dependencies = [ + "bytes", + "futures", + "h3 0.0.2 (git+https://github.com/hyperium/h3?branch=master)", "quinn", "quinn-proto", "tokio", @@ -444,11 +483,25 @@ dependencies = [ [[package]] name = "h3-webtransport" version = "0.1.0" -source = "git+https://github.com/security-union/h3?branch=add-webtransport#db5c723f653911a476bfd8ffcfebf0f8f2eb980d" +source = "git+https://github.com/security-union/h3?branch=add-webtransport#fa956e0d44e66c04545741908fcb3690b0890be6" dependencies = [ "bytes", "futures-util", - "h3", + "h3 0.0.2 (git+https://github.com/security-union/h3?branch=add-webtransport)", + "http", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "h3-webtransport" +version = "0.1.0" +source = "git+https://github.com/hyperium/h3?branch=master#3ef7c1a37b635e8446322d8f8d3a68580a208ad8" +dependencies = [ + "bytes", + "futures-util", + "h3 0.0.2 (git+https://github.com/hyperium/h3?branch=master)", "http", "pin-project-lite", "tokio", @@ -588,9 +641,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -617,13 +670,13 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -635,7 +688,7 @@ dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -646,18 +699,18 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.144" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "linux-raw-sys" @@ -667,9 +720,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -677,12 +730,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "memchr" @@ -708,18 +758,41 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] -name = "moq" +name = "moq-transport" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "clap", + "env_logger", + "futures", + "h3 0.0.2 (git+https://github.com/security-union/h3?branch=add-webtransport)", + "h3-quinn 0.0.2", + "h3-webtransport 0.1.0 (git+https://github.com/security-union/h3?branch=add-webtransport)", + "http", + "log", + "quinn", + "quinn-proto", + "ring", + "rustls 0.21.2", + "rustls-pemfile", + "thiserror", + "tokio", +] + +[[package]] +name = "moq-warp" version = "0.1.0" dependencies = [ "anyhow", @@ -727,19 +800,23 @@ dependencies = [ "clap", "env_logger", "futures", - "h3", - "h3-quinn", - "h3-webtransport", + "h3 0.0.2 (git+https://github.com/hyperium/h3?branch=master)", + "h3-quinn 0.0.3", + "h3-webtransport 0.1.0 (git+https://github.com/hyperium/h3?branch=master)", "hex", "http", "log", + "moq-transport", "mp4", + "paste", "quinn", + "quinn-proto", "ring", - "rustls 0.21.1", + "rustls 0.21.2", "rustls-pemfile", "serde", "serde_json", + "thiserror", "tokio", "warp", ] @@ -831,9 +908,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parking_lot" @@ -847,22 +924,28 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] -name = "percent-encoding" -version = "2.2.0" +name = "paste" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" @@ -904,9 +987,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -923,7 +1006,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.21.1", + "rustls 0.21.2", "thiserror", "tokio", "tracing", @@ -939,7 +1022,7 @@ dependencies = [ "rand", "ring", "rustc-hash", - "rustls 0.21.1", + "rustls 0.21.2", "slab", "thiserror", "tinyvec", @@ -956,14 +1039,14 @@ dependencies = [ "libc", "socket2 0.5.3", "tracing", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1000,18 +1083,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.8.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", @@ -1020,9 +1103,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "ring" @@ -1047,16 +1130,16 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1073,9 +1156,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" dependencies = [ "log", "ring", @@ -1089,7 +1172,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.1", + "base64 0.21.2", ] [[package]] @@ -1132,18 +1215,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", @@ -1225,7 +1308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1248,9 +1331,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -1303,9 +1386,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.1" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", @@ -1317,7 +1400,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.4.9", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1466,9 +1549,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -1487,9 +1570,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -1516,11 +1599,10 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -1564,9 +1646,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1574,9 +1656,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -1589,9 +1671,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1599,9 +1681,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -1612,15 +1694,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -1667,37 +1749,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] @@ -1706,93 +1764,51 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index dfd4eb4..da89776 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,41 +1,6 @@ -[package] -name = "moq" -version = "0.1.0" -edition = "2021" +[workspace] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -# Fork of h3 with WebTransport support -h3 = { git = "https://github.com/security-union/h3", branch = "add-webtransport" } -h3-quinn = { git = "https://github.com/security-union/h3", branch = "add-webtransport" } -h3-webtransport = { git = "https://github.com/security-union/h3", branch = "add-webtransport" } -quinn = { version = "0.10", default-features = false, features = ["runtime-tokio", "tls-rustls", "ring"] } - -# Crypto dependencies -ring = "0.16" -rustls = { version = "0.21", features = ["dangerous_configuration"] } -rustls-pemfile = "1.0.2" - -# Async stuff -tokio = { version = "1.27", features = ["full"] } -futures = "0.3" - -# Media -mp4 = "0.13.0" - -# Encoding -bytes = "1" -serde = "1.0.160" -serde_json = "1.0" - -# Web server to serve the fingerprint -http = "0.2" -warp = { version = "0.3.3", features = ["tls"] } -hex = "0.4.3" - -# Logging and utility -clap = { version = "4.0", features = [ "derive" ] } -log = { version = "0.4", features = ["std"] } -env_logger = "0.9.3" -anyhow = "1.0.70" +members = [ + "moq-transport", + "moq-warp" +] diff --git a/moq-transport/Cargo.toml b/moq-transport/Cargo.toml new file mode 100644 index 0000000..b5d3353 --- /dev/null +++ b/moq-transport/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "moq-transport" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Fork of h3 with WebTransport support +h3 = { git = "https://github.com/security-union/h3", branch = "add-webtransport" } +h3-quinn = { git = "https://github.com/security-union/h3", branch = "add-webtransport" } +h3-webtransport = { git = "https://github.com/security-union/h3", branch = "add-webtransport" } +quinn = { version = "0.10", default-features = false, features = ["runtime-tokio", "tls-rustls", "ring"] } +quinn-proto = "0.10" +http = "0.2" + +# Crypto dependencies +ring = "0.16" +rustls = { version = "0.21", features = ["dangerous_configuration"] } +rustls-pemfile = "1.0.2" + +# Async stuff +tokio = { version = "1.27", features = ["full"] } +futures = "0.3" + +# Encoding +bytes = "1" + +# Logging +clap = { version = "4.0", features = [ "derive" ] } +log = { version = "0.4", features = ["std"] } +env_logger = "0.9.3" + +# Utility +anyhow = "1.0.70" +thiserror = "1.0.21" +async-trait = "0.1" diff --git a/moq-transport/src/coding/decode.rs b/moq-transport/src/coding/decode.rs new file mode 100644 index 0000000..722b140 --- /dev/null +++ b/moq-transport/src/coding/decode.rs @@ -0,0 +1,54 @@ +use super::VarInt; +use bytes::Bytes; +use std::str; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncReadExt}; + +#[async_trait] +pub trait Decode: Sized { + async fn decode(r: &mut R) -> anyhow::Result; +} + +#[async_trait] +impl Decode for Bytes { + async fn decode(r: &mut R) -> anyhow::Result { + Vec::::decode(r).await.map(Bytes::from) + } +} + +#[async_trait] +impl Decode for Vec { + async fn decode(r: &mut R) -> anyhow::Result { + let size = u64::decode(r).await?; + + // NOTE: we don't use with_capacity since size is from an untrusted source + let mut buf = Vec::new(); + r.take(size).read_to_end(&mut buf).await?; + + Ok(buf) + } +} + +#[async_trait] +impl Decode for String { + async fn decode(r: &mut R) -> anyhow::Result { + let data = Vec::decode(r).await?; + let s = str::from_utf8(&data)?.to_string(); + Ok(s) + } +} + +#[async_trait] +impl Decode for u64 { + async fn decode(r: &mut R) -> anyhow::Result { + VarInt::decode(r).await.map(Into::into) + } +} + +#[async_trait] +impl Decode for usize { + async fn decode(r: &mut R) -> anyhow::Result { + VarInt::decode(r).await.map(Into::into) + } +} diff --git a/moq-transport/src/coding/duration.rs b/moq-transport/src/coding/duration.rs new file mode 100644 index 0000000..0091fce --- /dev/null +++ b/moq-transport/src/coding/duration.rs @@ -0,0 +1,24 @@ +use super::{Decode, Encode}; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +use std::time::Duration; + +#[async_trait] +impl Encode for Duration { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + let ms = self.as_millis(); + let ms = u64::try_from(ms)?; + ms.encode(w).await + } +} + +#[async_trait] +impl Decode for Duration { + async fn decode(r: &mut R) -> anyhow::Result { + let ms = u64::decode(r).await?; + let ms = ms; + Ok(Self::from_millis(ms)) + } +} diff --git a/moq-transport/src/coding/encode.rs b/moq-transport/src/coding/encode.rs new file mode 100644 index 0000000..fadfb30 --- /dev/null +++ b/moq-transport/src/coding/encode.rs @@ -0,0 +1,58 @@ +use async_trait::async_trait; +use tokio::io::{AsyncWrite, AsyncWriteExt}; + +use super::VarInt; +use bytes::Bytes; + +#[async_trait] +pub trait Encode: Sized { + async fn encode(&self, w: &mut W) -> anyhow::Result<()>; +} + +#[async_trait] +impl Encode for Bytes { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.len().encode(w).await?; + w.write_all(self).await?; + Ok(()) + } +} + +#[async_trait] +impl Encode for Vec { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.len().encode(w).await?; + w.write_all(self).await?; + Ok(()) + } +} + +#[async_trait] +impl Encode for &[u8] { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.len().encode(w).await?; + w.write_all(self).await?; + Ok(()) + } +} + +#[async_trait] +impl Encode for String { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.as_bytes().encode(w).await + } +} + +#[async_trait] +impl Encode for u64 { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + VarInt::try_from(*self)?.encode(w).await + } +} + +#[async_trait] +impl Encode for usize { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + VarInt::try_from(*self)?.encode(w).await + } +} diff --git a/moq-transport/src/coding/mod.rs b/moq-transport/src/coding/mod.rs new file mode 100644 index 0000000..a632307 --- /dev/null +++ b/moq-transport/src/coding/mod.rs @@ -0,0 +1,9 @@ +mod decode; +mod duration; +mod encode; +mod varint; + +pub use decode::*; +pub use duration::*; +pub use encode::*; +pub use varint::*; diff --git a/moq-transport/src/coding/varint.rs b/moq-transport/src/coding/varint.rs new file mode 100644 index 0000000..a7ca872 --- /dev/null +++ b/moq-transport/src/coding/varint.rs @@ -0,0 +1,139 @@ +// Based on quinn-proto +// https://github.com/quinn-rs/quinn/blob/main/quinn-proto/src/varint.rs +// Licensed via Apache 2.0 and MIT + +use std::convert::{TryFrom, TryInto}; +use std::fmt; + +use super::{Decode, Encode}; + +use thiserror::Error; + +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Error)] +#[error("value too large for varint encoding")] +pub struct BoundsExceeded; + +/// An integer less than 2^62 +/// +/// Values of this type are suitable for encoding as QUIC variable-length integer. +// It would be neat if we could express to Rust that the top two bits are available for use as enum +// discriminants +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub(crate) struct VarInt(u64); + +impl From for u64 { + fn from(x: VarInt) -> Self { + x.0 + } +} + +impl From for usize { + fn from(x: VarInt) -> Self { + x.0 as usize + } +} + +impl From for VarInt { + fn from(x: u8) -> Self { + Self(x.into()) + } +} + +impl From for VarInt { + fn from(x: u16) -> Self { + Self(x.into()) + } +} + +impl From for VarInt { + fn from(x: u32) -> Self { + Self(x.into()) + } +} + +impl TryFrom for VarInt { + type Error = BoundsExceeded; + + /// Succeeds iff `x` < 2^62 + fn try_from(x: u64) -> Result { + if x < 2u64.pow(62) { + Ok(Self(x)) + } else { + Err(BoundsExceeded) + } + } +} + +impl TryFrom for VarInt { + type Error = BoundsExceeded; + /// Succeeds iff `x` < 2^62 + fn try_from(x: usize) -> Result { + Self::try_from(x as u64) + } +} + +impl fmt::Debug for VarInt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for VarInt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +use async_trait::async_trait; + +#[async_trait] +impl Decode for VarInt { + async fn decode(r: &mut R) -> anyhow::Result { + let mut buf = [0; 8]; + r.read_exact(buf[0..1].as_mut()).await?; + + let tag = buf[0] >> 6; + buf[0] &= 0b0011_1111; + + let x = match tag { + 0b00 => u64::from(buf[0]), + 0b01 => { + r.read_exact(buf[1..2].as_mut()).await?; + u64::from(u16::from_be_bytes(buf[..2].try_into().unwrap())) + } + 0b10 => { + r.read_exact(buf[1..4].as_mut()).await?; + u64::from(u32::from_be_bytes(buf[..4].try_into().unwrap())) + } + 0b11 => { + r.read_exact(buf[1..8].as_mut()).await?; + u64::from_be_bytes(buf) + } + _ => unreachable!(), + }; + + Ok(Self(x)) + } +} + +#[async_trait] +impl Encode for VarInt { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + let x = self.0; + if x < 2u64.pow(6) { + w.write_u8(x as u8).await?; + } else if x < 2u64.pow(14) { + w.write_u16(0b01 << 14 | x as u16).await?; + } else if x < 2u64.pow(30) { + w.write_u32(0b10 << 30 | x as u32).await?; + } else if x < 2u64.pow(62) { + w.write_u64(0b11 << 62 | x).await?; + } else { + anyhow::bail!("malformed VarInt"); + } + + Ok(()) + } +} diff --git a/moq-transport/src/control/announce.rs b/moq-transport/src/control/announce.rs new file mode 100644 index 0000000..1bffe11 --- /dev/null +++ b/moq-transport/src/control/announce.rs @@ -0,0 +1,26 @@ +use crate::coding::{Decode, Encode}; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +#[derive(Debug)] +pub struct Announce { + // The track namespace + pub track_namespace: String, +} + +#[async_trait] +impl Decode for Announce { + async fn decode(r: &mut R) -> anyhow::Result { + let track_namespace = String::decode(r).await?; + Ok(Self { track_namespace }) + } +} + +#[async_trait] +impl Encode for Announce { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.track_namespace.encode(w).await?; + Ok(()) + } +} diff --git a/moq-transport/src/control/announce_error.rs b/moq-transport/src/control/announce_error.rs new file mode 100644 index 0000000..09a56c1 --- /dev/null +++ b/moq-transport/src/control/announce_error.rs @@ -0,0 +1,43 @@ +use crate::coding::{Decode, Encode}; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +#[derive(Debug)] +pub struct AnnounceError { + // Echo back the namespace that was announced. + // TODO Propose using an ID to save bytes. + pub track_namespace: String, + + // An error code. + pub code: u64, + + // An optional, human-readable reason. + pub reason: String, +} + +#[async_trait] +impl Decode for AnnounceError { + async fn decode(r: &mut R) -> anyhow::Result { + let track_namespace = String::decode(r).await?; + let code = u64::decode(r).await?; + let reason = String::decode(r).await?; + + Ok(Self { + track_namespace, + code, + reason, + }) + } +} + +#[async_trait] +impl Encode for AnnounceError { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.track_namespace.encode(w).await?; + self.code.encode(w).await?; + self.reason.encode(w).await?; + + Ok(()) + } +} diff --git a/moq-transport/src/control/announce_ok.rs b/moq-transport/src/control/announce_ok.rs new file mode 100644 index 0000000..63049ad --- /dev/null +++ b/moq-transport/src/control/announce_ok.rs @@ -0,0 +1,26 @@ +use crate::coding::{Decode, Encode}; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +#[derive(Debug)] +pub struct AnnounceOk { + // Echo back the namespace that was announced. + // TODO Propose using an ID to save bytes. + pub track_namespace: String, +} + +#[async_trait] +impl Decode for AnnounceOk { + async fn decode(r: &mut R) -> anyhow::Result { + let track_namespace = String::decode(r).await?; + Ok(Self { track_namespace }) + } +} + +#[async_trait] +impl Encode for AnnounceOk { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.track_namespace.encode(w).await + } +} diff --git a/moq-transport/src/control/go_away.rs b/moq-transport/src/control/go_away.rs new file mode 100644 index 0000000..6d798db --- /dev/null +++ b/moq-transport/src/control/go_away.rs @@ -0,0 +1,24 @@ +use crate::coding::{Decode, Encode}; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +#[derive(Debug)] +pub struct GoAway { + pub url: String, +} + +#[async_trait] +impl Decode for GoAway { + async fn decode(r: &mut R) -> anyhow::Result { + let url = String::decode(r).await?; + Ok(Self { url }) + } +} + +#[async_trait] +impl Encode for GoAway { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.url.encode(w).await + } +} diff --git a/moq-transport/src/control/mod.rs b/moq-transport/src/control/mod.rs new file mode 100644 index 0000000..54c4e37 --- /dev/null +++ b/moq-transport/src/control/mod.rs @@ -0,0 +1,110 @@ +mod announce; +mod announce_error; +mod announce_ok; +mod go_away; +mod stream; +mod subscribe; +mod subscribe_error; +mod subscribe_ok; + +pub use announce::*; +pub use announce_error::*; +pub use announce_ok::*; +pub use go_away::*; +pub use stream::*; +pub use subscribe::*; +pub use subscribe_error::*; +pub use subscribe_ok::*; + +use crate::coding::{Decode, Encode}; + +use async_trait::async_trait; +use std::fmt; +use tokio::io::{AsyncRead, AsyncWrite}; + +use anyhow::Context; + +// Use a macro to generate the message types rather than copy-paste. +// This implements a decode/encode method that uses the specified type. +macro_rules! message_types { + {$($name:ident = $val:expr,)*} => { + pub enum Message { + $($name($name)),* + } + + #[async_trait] + impl Decode for Message { + async fn decode(r: &mut R) -> anyhow::Result { + let t = u64::decode(r).await.context("failed to decode type")?; + + Ok(match u64::from(t) { + $($val => { + let msg = $name::decode(r).await.context(concat!("failed to decode ", stringify!($name)))?; + Self::$name(msg) + })* + _ => anyhow::bail!("invalid type: {}", t), + }) + } + } + + #[async_trait] + impl Encode for Message { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + match self { + $(Self::$name(ref m) => { + let id: u64 = $val; // tell the compiler this is a u64 + id.encode(w).await.context("failed to encode type")?; + m.encode(w).await.context("failed to encode message") + },)* + } + } + } + + // Unwrap the enum into the specified type. + $(impl TryFrom for $name { + type Error = anyhow::Error; + + fn try_from(m: Message) -> Result { + match m { + Message::$name(m) => Ok(m), + _ => anyhow::bail!("invalid message type"), + } + } + })* + + $(impl From<$name> for Message { + fn from(m: $name) -> Self { + Message::$name(m) + } + })* + + impl fmt::Debug for Message { + // Delegate to the message formatter + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + $(Self::$name(ref m) => m.fmt(f),)* + } + } + } + } +} + +// NOTE: These messages are forked from moq-transport-00. +// 1. subscribe specifies the track_id, not subscribe_ok +// 2. messages lack a specified length +// 3. optional parameters are not supported (announce, subscribe) +// 4. not allowed on undirectional streams; only after SETUP on the bidirectional stream + +// Each message is prefixed with the given VarInt type. +message_types! { + // NOTE: Object and Setup are in other modules. + // Object = 0x0 + // Setup = 0x1 + Subscribe = 0x3, + SubscribeOk = 0x4, + SubscribeError = 0x5, + Announce = 0x6, + AnnounceOk = 0x7, + AnnounceError = 0x8, + GoAway = 0x10, +} diff --git a/moq-transport/src/control/stream.rs b/moq-transport/src/control/stream.rs new file mode 100644 index 0000000..6d5eb57 --- /dev/null +++ b/moq-transport/src/control/stream.rs @@ -0,0 +1,61 @@ +use crate::coding::{Decode, Encode}; +use crate::control::Message; + +use bytes::Bytes; + +use h3::quic::BidiStream; + +pub struct Stream { + sender: SendStream, + recver: RecvStream, +} + +impl Stream { + pub(crate) fn new(stream: h3_webtransport::stream::BidiStream, Bytes>) -> Self { + let (sender, recver) = stream.split(); + let sender = SendStream::new(sender); + let recver = RecvStream::new(recver); + + Self { sender, recver } + } + + pub fn split(self) -> (SendStream, RecvStream) { + (self.sender, self.recver) + } + + pub async fn send(&mut self, msg: Message) -> anyhow::Result<()> { + self.sender.send(msg).await + } + + pub async fn recv(&mut self) -> anyhow::Result { + self.recver.recv().await + } +} + +pub struct SendStream { + stream: h3_webtransport::stream::SendStream, Bytes>, +} + +impl SendStream { + pub(crate) fn new(stream: h3_webtransport::stream::SendStream, Bytes>) -> Self { + Self { stream } + } + + pub async fn send(&mut self, msg: Message) -> anyhow::Result<()> { + msg.encode(&mut self.stream).await + } +} + +pub struct RecvStream { + stream: h3_webtransport::stream::RecvStream, +} + +impl RecvStream { + pub(crate) fn new(stream: h3_webtransport::stream::RecvStream) -> Self { + Self { stream } + } + + pub async fn recv(&mut self) -> anyhow::Result { + Message::decode(&mut self.stream).await + } +} diff --git a/moq-transport/src/control/subscribe.rs b/moq-transport/src/control/subscribe.rs new file mode 100644 index 0000000..aabece4 --- /dev/null +++ b/moq-transport/src/control/subscribe.rs @@ -0,0 +1,43 @@ +use crate::coding::{Decode, Encode}; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +#[derive(Debug)] +pub struct Subscribe { + // An ID we choose so we can map to the track_name. + // Proposal: https://github.com/moq-wg/moq-transport/issues/209 + pub track_id: u64, + + // The track namespace. + pub track_namespace: String, + + // The track name. + pub track_name: String, +} + +#[async_trait] +impl Decode for Subscribe { + async fn decode(r: &mut R) -> anyhow::Result { + let track_id = u64::decode(r).await?; + let track_namespace = String::decode(r).await?; + let track_name = String::decode(r).await?; + + Ok(Self { + track_id, + track_namespace, + track_name, + }) + } +} + +#[async_trait] +impl Encode for Subscribe { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.track_id.encode(w).await?; + self.track_namespace.encode(w).await?; + self.track_name.encode(w).await?; + + Ok(()) + } +} diff --git a/moq-transport/src/control/subscribe_error.rs b/moq-transport/src/control/subscribe_error.rs new file mode 100644 index 0000000..8173246 --- /dev/null +++ b/moq-transport/src/control/subscribe_error.rs @@ -0,0 +1,40 @@ +use crate::coding::{Decode, Encode}; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +#[derive(Debug)] +pub struct SubscribeError { + // NOTE: No full track name because of this proposal: https://github.com/moq-wg/moq-transport/issues/209 + + // The ID for this track. + pub track_id: u64, + + // An error code. + pub code: u64, + + // An optional, human-readable reason. + pub reason: String, +} + +#[async_trait] +impl Decode for SubscribeError { + async fn decode(r: &mut R) -> anyhow::Result { + let track_id = u64::decode(r).await?; + let code = u64::decode(r).await?; + let reason = String::decode(r).await?; + + Ok(Self { track_id, code, reason }) + } +} + +#[async_trait] +impl Encode for SubscribeError { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.track_id.encode(w).await?; + self.code.encode(w).await?; + self.reason.encode(w).await?; + + Ok(()) + } +} diff --git a/moq-transport/src/control/subscribe_ok.rs b/moq-transport/src/control/subscribe_ok.rs new file mode 100644 index 0000000..3c1cc53 --- /dev/null +++ b/moq-transport/src/control/subscribe_ok.rs @@ -0,0 +1,39 @@ +use crate::coding::{Decode, Encode}; + +use std::time::Duration; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +#[derive(Debug)] +pub struct SubscribeOk { + // NOTE: No full track name because of this proposal: https://github.com/moq-wg/moq-transport/issues/209 + + // The ID for this track. + pub track_id: u64, + + // The subscription will end after this duration has elapsed. + // A value of zero is invalid. + pub expires: Option, +} + +#[async_trait] +impl Decode for SubscribeOk { + async fn decode(r: &mut R) -> anyhow::Result { + let track_id = u64::decode(r).await?; + let expires = Duration::decode(r).await?; + let expires = if expires == Duration::ZERO { None } else { Some(expires) }; + + Ok(Self { track_id, expires }) + } +} + +#[async_trait] +impl Encode for SubscribeOk { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.track_id.encode(w).await?; + self.expires.unwrap_or_default().encode(w).await?; + + Ok(()) + } +} diff --git a/moq-transport/src/data/header.rs b/moq-transport/src/data/header.rs new file mode 100644 index 0000000..9e18219 --- /dev/null +++ b/moq-transport/src/data/header.rs @@ -0,0 +1,56 @@ +use crate::coding::{Decode, Encode}; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +// Another name for OBJECT, sent as a header for data streams. +#[derive(Debug)] +pub struct Header { + // An ID for this track. + // Proposal: https://github.com/moq-wg/moq-transport/issues/209 + pub track_id: u64, + + // The group sequence number. + pub group_sequence: u64, + + // The object sequence number. + pub object_sequence: u64, + + // The priority/send order. + pub send_order: u64, +} + +#[async_trait] +impl Decode for Header { + async fn decode(r: &mut R) -> anyhow::Result { + let typ = u64::decode(r).await?; + anyhow::ensure!(typ == 0, "OBJECT type must be 0"); + + // NOTE: size has been omitted + + let track_id = u64::decode(r).await?; + let group_sequence = u64::decode(r).await?; + let object_sequence = u64::decode(r).await?; + let send_order = u64::decode(r).await?; + + Ok(Self { + track_id, + group_sequence, + object_sequence, + send_order, + }) + } +} + +#[async_trait] +impl Encode for Header { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + 0u64.encode(w).await?; + self.track_id.encode(w).await?; + self.group_sequence.encode(w).await?; + self.object_sequence.encode(w).await?; + self.send_order.encode(w).await?; + + Ok(()) + } +} diff --git a/moq-transport/src/data/mod.rs b/moq-transport/src/data/mod.rs new file mode 100644 index 0000000..bd5b430 --- /dev/null +++ b/moq-transport/src/data/mod.rs @@ -0,0 +1,5 @@ +mod header; +mod transport; + +pub use header::*; +pub use transport::*; diff --git a/moq-transport/src/data/transport.rs b/moq-transport/src/data/transport.rs new file mode 100644 index 0000000..832cd82 --- /dev/null +++ b/moq-transport/src/data/transport.rs @@ -0,0 +1,49 @@ +use super::Header; +use anyhow::Context; +use bytes::Bytes; + +use crate::coding::{Decode, Encode}; + +// TODO support clients +type WebTransportSession = h3_webtransport::server::WebTransportSession; + +// Reduce some typing for implementors. +pub type SendStream = h3_webtransport::stream::SendStream, Bytes>; +pub type RecvStream = h3_webtransport::stream::RecvStream; + +pub struct Transport { + transport: WebTransportSession, +} + +impl Transport { + pub fn new(transport: WebTransportSession) -> Self { + Self { transport } + } + + pub async fn recv(&self) -> anyhow::Result<(Header, RecvStream)> { + let (_session_id, mut stream) = self + .transport + .accept_uni() + .await + .context("failed to accept uni stream")? + .context("no uni stream")?; + + let header = Header::decode(&mut stream).await?; + + Ok((header, stream)) + } + + pub async fn send(&self, header: Header) -> anyhow::Result { + let mut stream = self + .transport + .open_uni(self.transport.session_id()) + .await + .context("failed to open uni stream")?; + + // TODO set send_order based on header + + header.encode(&mut stream).await?; + + Ok(stream) + } +} diff --git a/moq-transport/src/lib.rs b/moq-transport/src/lib.rs new file mode 100644 index 0000000..7a4785f --- /dev/null +++ b/moq-transport/src/lib.rs @@ -0,0 +1,5 @@ +pub mod coding; +pub mod control; +pub mod data; +pub mod server; +pub mod setup; diff --git a/moq-transport/src/server/endpoint.rs b/moq-transport/src/server/endpoint.rs new file mode 100644 index 0000000..7ab1e61 --- /dev/null +++ b/moq-transport/src/server/endpoint.rs @@ -0,0 +1,42 @@ +use super::handshake::{Accept, Connecting}; + +use anyhow::Context; +use tokio::task::JoinSet; + +pub struct Endpoint { + // The QUIC server, yielding new connections and sessions. + endpoint: quinn::Endpoint, + + // A list of connections that are completing the WebTransport handshake. + handshake: JoinSet>, +} + +impl Endpoint { + pub fn new(endpoint: quinn::Endpoint) -> Self { + let handshake = JoinSet::new(); + Self { endpoint, handshake } + } + + // Accept the next WebTransport session. + pub async fn accept(&mut self) -> anyhow::Result { + loop { + tokio::select!( + // Accept the connection and start the WebTransport handshake. + conn = self.endpoint.accept() => { + let conn = conn.context("failed to accept connection")?; + self.handshake.spawn(async move { + Connecting::new(conn).accept().await + }); + }, + // Return any mostly finished WebTransport handshakes. + res = self.handshake.join_next(), if !self.handshake.is_empty() => { + let res = res.expect("no tasks").expect("task aborted"); + match res { + Ok(session) => return Ok(session), + Err(err) => log::warn!("failed to accept session: {:?}", err), + } + }, + ) + } + } +} diff --git a/moq-transport/src/server/handshake.rs b/moq-transport/src/server/handshake.rs new file mode 100644 index 0000000..da5816f --- /dev/null +++ b/moq-transport/src/server/handshake.rs @@ -0,0 +1,114 @@ +use super::setup::{RecvSetup, SendSetup}; +use crate::{control, data, setup}; + +use anyhow::Context; +use bytes::Bytes; + +pub struct Connecting { + conn: quinn::Connecting, +} + +impl Connecting { + pub fn new(conn: quinn::Connecting) -> Self { + Self { conn } + } + + pub async fn accept(self) -> anyhow::Result { + let conn = self.conn.await.context("failed to accept h3 connection")?; + + let mut conn = h3::server::builder() + .enable_webtransport(true) + .enable_connect(true) + .enable_datagram(true) + .max_webtransport_sessions(1) + .send_grease(true) + .build(h3_quinn::Connection::new(conn)) + .await + .context("failed to create h3 server")?; + + let (req, stream) = conn + .accept() + .await + .context("failed to accept h3 session")? + .context("failed to accept h3 request")?; + + let ext = req.extensions(); + anyhow::ensure!(req.method() == http::Method::CONNECT, "expected CONNECT request"); + anyhow::ensure!( + ext.get::() == Some(&h3::ext::Protocol::WEB_TRANSPORT), + "expected WebTransport CONNECT" + ); + + // Return the request after validating the bare minimum. + let accept = Accept { conn, req, stream }; + + Ok(accept) + } +} + +// The WebTransport handshake is complete, but we need to decide if we accept it or return 404. +pub struct Accept { + // Inspect to decide whether to accept() or reject() the session. + req: http::Request<()>, + + conn: h3::server::Connection, + stream: h3::server::RequestStream, Bytes>, +} + +impl Accept { + // Expose the received URI + pub fn uri(&self) -> &http::Uri { + self.req.uri() + } + + // Accept the WebTransport session. + pub async fn accept(self) -> anyhow::Result { + let transport = h3_webtransport::server::WebTransportSession::accept(self.req, self.stream, self.conn).await?; + + let stream = transport + .accept_bi() + .await + .context("failed to accept bidi stream")? + .unwrap(); + + let transport = data::Transport::new(transport); + + let stream = match stream { + h3_webtransport::server::AcceptedBi::BidiStream(_session_id, stream) => stream, + h3_webtransport::server::AcceptedBi::Request(..) => anyhow::bail!("additional http requests not supported"), + }; + + let setup = RecvSetup::new(stream).recv().await?; + + Ok(Setup { transport, setup }) + } + + // Reject the WebTransport session with a HTTP response. + pub async fn reject(mut self, resp: http::Response<()>) -> anyhow::Result<()> { + self.stream.send_response(resp).await?; + Ok(()) + } +} + +pub struct Setup { + setup: SendSetup, + transport: data::Transport, +} + +impl Setup { + // Return the setup message we received. + pub fn setup(&self) -> &setup::Client { + &self.setup.client + } + + // Accept the session with our own setup message. + pub async fn accept(self, setup: setup::Server) -> anyhow::Result<(data::Transport, control::Stream)> { + let control = self.setup.send(setup).await?; + Ok((self.transport, control)) + } + + pub async fn reject(self) -> anyhow::Result<()> { + // TODO Close the QUIC connection with an error code. + Ok(()) + } +} diff --git a/moq-transport/src/server/mod.rs b/moq-transport/src/server/mod.rs new file mode 100644 index 0000000..80df7ee --- /dev/null +++ b/moq-transport/src/server/mod.rs @@ -0,0 +1,6 @@ +mod endpoint; +mod handshake; +mod setup; + +pub use endpoint::*; +pub use handshake::*; diff --git a/moq-transport/src/server/setup.rs b/moq-transport/src/server/setup.rs new file mode 100644 index 0000000..0e821af --- /dev/null +++ b/moq-transport/src/server/setup.rs @@ -0,0 +1,42 @@ +use crate::coding::{Decode, Encode}; +use crate::{control, setup}; + +use anyhow::Context; +use bytes::Bytes; + +pub(crate) struct RecvSetup { + stream: h3_webtransport::stream::BidiStream, Bytes>, +} + +impl RecvSetup { + pub fn new(stream: h3_webtransport::stream::BidiStream, Bytes>) -> Self { + Self { stream } + } + + pub async fn recv(mut self) -> anyhow::Result { + let setup = setup::Client::decode(&mut self.stream) + .await + .context("failed to read client SETUP message")?; + + Ok(SendSetup::new(self.stream, setup)) + } +} + +pub(crate) struct SendSetup { + pub client: setup::Client, + stream: h3_webtransport::stream::BidiStream, Bytes>, +} + +impl SendSetup { + pub fn new( + stream: h3_webtransport::stream::BidiStream, Bytes>, + client: setup::Client, + ) -> Self { + Self { stream, client } + } + + pub async fn send(mut self, setup: setup::Server) -> anyhow::Result { + setup.encode(&mut self.stream).await?; + Ok(control::Stream::new(self.stream)) + } +} diff --git a/moq-transport/src/setup/client.rs b/moq-transport/src/setup/client.rs new file mode 100644 index 0000000..48fea2d --- /dev/null +++ b/moq-transport/src/setup/client.rs @@ -0,0 +1,54 @@ +use super::{Role, Versions}; +use crate::coding::{Decode, Encode}; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +use anyhow::Context; + +// Sent by the client to setup up the session. +#[derive(Debug)] +pub struct Client { + // NOTE: This is not a message type, but rather the control stream header. + // Proposal: https://github.com/moq-wg/moq-transport/issues/138 + + // The list of supported versions in preferred order. + pub versions: Versions, + + // Indicate if the client is a publisher, a subscriber, or both. + // Proposal: moq-wg/moq-transport#151 + pub role: Role, + + // The path, non-empty ONLY when not using WebTransport. + pub path: String, +} + +#[async_trait] +impl Decode for Client { + async fn decode(r: &mut R) -> anyhow::Result { + let typ = u64::decode(r).await.context("failed to read type")?; + anyhow::ensure!(typ == 1, "client SETUP must be type 1"); + + let versions = Versions::decode(r).await.context("failed to read supported versions")?; + anyhow::ensure!(!versions.is_empty(), "client must support at least one version"); + + let role = Role::decode(r).await.context("failed to decode role")?; + let path = String::decode(r).await.context("failed to read path")?; + + Ok(Self { versions, role, path }) + } +} + +#[async_trait] +impl Encode for Client { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + 1u64.encode(w).await?; + + anyhow::ensure!(!self.versions.is_empty(), "client must support at least one version"); + self.versions.encode(w).await?; + self.role.encode(w).await?; + self.path.encode(w).await?; + + Ok(()) + } +} diff --git a/moq-transport/src/setup/mod.rs b/moq-transport/src/setup/mod.rs new file mode 100644 index 0000000..e420112 --- /dev/null +++ b/moq-transport/src/setup/mod.rs @@ -0,0 +1,15 @@ +mod client; +mod role; +mod server; +mod version; + +pub use client::*; +pub use role::*; +pub use server::*; +pub use version::*; + +// NOTE: These are forked from moq-transport-00. +// 1. messages lack a sized length +// 2. parameters are not optional and written in order (role + path) +// 3. role indicates local support only, not remote support +// 4. server setup is id=2 to disambiguate diff --git a/moq-transport/src/setup/role.rs b/moq-transport/src/setup/role.rs new file mode 100644 index 0000000..e42c5e3 --- /dev/null +++ b/moq-transport/src/setup/role.rs @@ -0,0 +1,65 @@ +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +use crate::coding::{Decode, Encode}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Role { + Publisher, + Subscriber, + Both, +} + +impl Role { + pub fn is_publisher(&self) -> bool { + match self { + Self::Publisher | Self::Both => true, + Self::Subscriber => false, + } + } + + pub fn is_subscriber(&self) -> bool { + match self { + Self::Subscriber | Self::Both => true, + Self::Publisher => false, + } + } +} + +impl From for u64 { + fn from(r: Role) -> Self { + match r { + Role::Publisher => 0x0, + Role::Subscriber => 0x1, + Role::Both => 0x2, + } + } +} + +impl TryFrom for Role { + type Error = anyhow::Error; + + fn try_from(v: u64) -> Result { + Ok(match v { + 0x0 => Self::Publisher, + 0x1 => Self::Subscriber, + 0x2 => Self::Both, + _ => anyhow::bail!("invalid role: {}", v), + }) + } +} + +#[async_trait] +impl Decode for Role { + async fn decode(r: &mut R) -> anyhow::Result { + let v = u64::decode(r).await?; + v.try_into() + } +} + +#[async_trait] +impl Encode for Role { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + u64::from(*self).encode(w).await + } +} diff --git a/moq-transport/src/setup/server.rs b/moq-transport/src/setup/server.rs new file mode 100644 index 0000000..c319cda --- /dev/null +++ b/moq-transport/src/setup/server.rs @@ -0,0 +1,44 @@ +use super::{Role, Version}; +use crate::coding::{Decode, Encode}; + +use anyhow::Context; +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +// Sent by the server in response to a client. +// NOTE: This is not a message type, but rather the control stream header. +// Proposal: https://github.com/moq-wg/moq-transport/issues/138 +#[derive(Debug)] +pub struct Server { + // The list of supported versions in preferred order. + pub version: Version, + + // param: 0x0: Indicate if the server is a publisher, a subscriber, or both. + // Proposal: moq-wg/moq-transport#151 + pub role: Role, +} + +#[async_trait] +impl Decode for Server { + async fn decode(r: &mut R) -> anyhow::Result { + let typ = u64::decode(r).await.context("failed to read type")?; + anyhow::ensure!(typ == 2, "server SETUP must be type 2"); + + let version = Version::decode(r).await.context("failed to read version")?; + let role = Role::decode(r).await.context("failed to read role")?; + + Ok(Self { version, role }) + } +} + +#[async_trait] +impl Encode for Server { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + 2u64.encode(w).await?; // setup type + + self.version.encode(w).await?; + self.role.encode(w).await?; + + Ok(()) + } +} diff --git a/moq-transport/src/setup/version.rs b/moq-transport/src/setup/version.rs new file mode 100644 index 0000000..38a6ac8 --- /dev/null +++ b/moq-transport/src/setup/version.rs @@ -0,0 +1,77 @@ +use crate::coding::{Decode, Encode}; + +use async_trait::async_trait; +use tokio::io::{AsyncRead, AsyncWrite}; + +use std::ops::Deref; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Version(pub u64); + +impl Version { + pub const DRAFT_00: Version = Version(0xff00); +} + +impl From for Version { + fn from(v: u64) -> Self { + Self(v) + } +} + +impl From for u64 { + fn from(v: Version) -> Self { + v.0 + } +} + +#[async_trait] +impl Decode for Version { + async fn decode(r: &mut R) -> anyhow::Result { + let v = u64::decode(r).await?; + Ok(Self(v)) + } +} + +#[async_trait] +impl Encode for Version { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.0.encode(w).await + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Versions(pub Vec); + +#[async_trait] +impl Decode for Versions { + async fn decode(r: &mut R) -> anyhow::Result { + let count = u64::decode(r).await?; + let mut vs = Vec::new(); + + for _ in 0..count { + let v = Version::decode(r).await?; + vs.push(v); + } + + Ok(Self(vs)) + } +} + +#[async_trait] +impl Encode for Versions { + async fn encode(&self, w: &mut W) -> anyhow::Result<()> { + self.0.len().encode(w).await?; + for v in &self.0 { + v.encode(w).await?; + } + Ok(()) + } +} + +impl Deref for Versions { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/moq-warp/Cargo.lock b/moq-warp/Cargo.lock new file mode 100644 index 0000000..93b90f0 --- /dev/null +++ b/moq-warp/Cargo.lock @@ -0,0 +1,1809 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1e31e207a6b8fb791a38ea3105e6cb541f55e4d029902d3039a4ad07cc4105" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "cpufeatures" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h3" +version = "0.0.2" +source = "git+https://github.com/security-union/h3?branch=add-webtransport#db5c723f653911a476bfd8ffcfebf0f8f2eb980d" +dependencies = [ + "bytes", + "fastrand", + "futures-util", + "http", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "h3-quinn" +version = "0.0.2" +source = "git+https://github.com/security-union/h3?branch=add-webtransport#db5c723f653911a476bfd8ffcfebf0f8f2eb980d" +dependencies = [ + "bytes", + "futures", + "h3", + "quinn", + "quinn-proto", + "tokio", + "tokio-util", +] + +[[package]] +name = "h3-webtransport" +version = "0.1.0" +source = "git+https://github.com/security-union/h3?branch=add-webtransport#db5c723f653911a476bfd8ffcfebf0f8f2eb980d" +dependencies = [ + "bytes", + "futures-util", + "h3", + "http", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64 0.13.1", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + +[[package]] +name = "moq" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "clap", + "env_logger", + "futures", + "h3", + "h3-quinn", + "h3-webtransport", + "hex", + "http", + "log", + "mp4", + "paste", + "quinn", + "quinn-proto", + "ring", + "rustls 0.21.1", + "rustls-pemfile", + "serde", + "serde_json", + "thiserror", + "tokio", + "warp", +] + +[[package]] +name = "mp4" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509348cba250e7b852a875100a2ddce7a36ee3abf881a681c756670c1774264d" +dependencies = [ + "byteorder", + "bytes", + "num-rational", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin 0.9.8", + "version_check", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21252f1c0fc131f1b69182db8f34837e8a69737b8251dff75636a9be0518c324" +dependencies = [ + "bytes", + "futures-io", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.21.1", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85af4ed6ee5a89f26a26086e9089a6643650544c025158449a3626ebf72884b3" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls 0.21.1", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df19e284d93757a9fb91d63672f7741b129246a669db09d1c0063071debc0c0" +dependencies = [ + "bytes", + "libc", + "socket2 0.5.3", + "tracing", + "windows-sys 0.48.0", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.1", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.4.9", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.8", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" + +[[package]] +name = "web-sys" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/moq-warp/Cargo.toml b/moq-warp/Cargo.toml new file mode 100644 index 0000000..bc53dcc --- /dev/null +++ b/moq-warp/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "moq-warp" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# WebTransport support: TODO pin a version when released +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" } +quinn = { version = "0.10", default-features = false, features = ["runtime-tokio", "tls-rustls", "ring"] } +quinn-proto = "0.10" + +# Crypto dependencies +ring = "0.16" +rustls = { version = "0.21", features = ["dangerous_configuration"] } +rustls-pemfile = "1.0.2" + +# Async stuff +tokio = { version = "1.27", features = ["full"] } +futures = "0.3" + +# Media +mp4 = "0.13.0" + +# Encoding +bytes = "1" +serde = "1.0.160" +serde_json = "1.0" + +# Web server to serve the fingerprint +http = "0.2" +warp = { version = "0.3.3", features = ["tls"] } +hex = "0.4.3" + +# Logging +clap = { version = "4.0", features = [ "derive" ] } +log = { version = "0.4", features = ["std"] } +env_logger = "0.9.3" + +# Utility +anyhow = "1.0.70" +thiserror = "1.0.21" +paste = "1.0" + +moq-transport = { path = "../moq-transport" } diff --git a/moq-warp/src/app/mod.rs b/moq-warp/src/app/mod.rs new file mode 100644 index 0000000..9efc4ca --- /dev/null +++ b/moq-warp/src/app/mod.rs @@ -0,0 +1,5 @@ +mod server; +mod session; + +pub use server::*; +pub use session::*; diff --git a/src/app/server.rs b/moq-warp/src/app/server.rs similarity index 55% rename from src/app/server.rs rename to moq-warp/src/app/server.rs index bdbbdd0..25fda11 100644 --- a/src/app/server.rs +++ b/moq-warp/src/app/server.rs @@ -3,16 +3,20 @@ use crate::media; use std::{fs, io, net, path, sync, time}; -use super::WebTransportSession; - use anyhow::Context; -pub struct Server { - // The QUIC server, yielding new connections and sessions. - server: quinn::Endpoint, +use moq_transport::server; +use tokio::task::JoinSet; - // The media source - broadcast: media::Broadcast, +pub struct Server { + // The MoQ transport server. + server: server::Endpoint, + + // The media source. + broadcasts: media::Broadcasts, + + // Sessions actively being run. + sessions: JoinSet>, } pub struct ServerConfig { @@ -20,7 +24,7 @@ pub struct ServerConfig { pub cert: path::PathBuf, pub key: path::PathBuf, - pub broadcast: media::Broadcast, + pub broadcasts: media::Broadcasts, } impl Server { @@ -70,56 +74,38 @@ impl Server { server_config.transport = sync::Arc::new(transport_config); let server = quinn::Endpoint::server(server_config, config.addr)?; - let broadcast = config.broadcast; + let broadcasts = config.broadcasts; - Ok(Self { server, broadcast }) + let server = server::Endpoint::new(server); + let sessions = JoinSet::new(); + + Ok(Self { + server, + broadcasts, + sessions, + }) } pub async fn run(&mut self) -> anyhow::Result<()> { loop { - let conn = self.server.accept().await.context("failed to accept connection")?; - let broadcast = self.broadcast.clone(); + tokio::select! { + res = self.server.accept() => { + let session = res.context("failed to accept connection")?; + let broadcasts = self.broadcasts.clone(); - tokio::spawn(async move { - let session = Self::accept_session(conn).await.context("failed to accept session")?; + self.sessions.spawn(async move { + let session: Session = Session::accept(session, broadcasts).await?; + session.serve().await + }); + }, + res = self.sessions.join_next(), if !self.sessions.is_empty() => { + let res = res.expect("no tasks").expect("task aborted"); - // Use a wrapper run the session. - let session = Session::new(session); - session.serve_broadcast(broadcast).await - }); + if let Err(err) = res { + log::error!("session terminated: {:?}", err); + } + }, + } } } - - async fn accept_session(conn: quinn::Connecting) -> anyhow::Result { - let conn = conn.await.context("failed to accept h3 connection")?; - - let mut conn = h3::server::builder() - .enable_webtransport(true) - .enable_connect(true) - .enable_datagram(true) - .max_webtransport_sessions(1) - .send_grease(true) - .build(h3_quinn::Connection::new(conn)) - .await - .context("failed to create h3 server")?; - - let (req, stream) = conn - .accept() - .await - .context("failed to accept h3 session")? - .context("failed to accept h3 request")?; - - let ext = req.extensions(); - anyhow::ensure!(req.method() == http::Method::CONNECT, "expected CONNECT request"); - anyhow::ensure!( - ext.get::() == Some(&h3::ext::Protocol::WEB_TRANSPORT), - "expected WebTransport CONNECT" - ); - - let session = WebTransportSession::accept(req, stream, conn) - .await - .context("failed to accept WebTransport session")?; - - Ok(session) - } } diff --git a/moq-warp/src/app/session.rs b/moq-warp/src/app/session.rs new file mode 100644 index 0000000..80cf950 --- /dev/null +++ b/moq-warp/src/app/session.rs @@ -0,0 +1,225 @@ +use crate::media; + +use anyhow::Context; + +use tokio::io::AsyncWriteExt; +use tokio::task::JoinSet; + +use std::sync::Arc; + +use moq_transport::{control, data, server, setup}; + +pub struct Session { + // Used to send/receive data streams. + transport: Arc, + + // Used to send/receive control messages. + control: control::Stream, + + // The list of available broadcasts for the session. + media: media::Broadcasts, + + // The list of active subscriptions. + tasks: JoinSet>, +} + +impl Session { + pub async fn accept(session: server::Accept, media: media::Broadcasts) -> anyhow::Result { + // Accep the WebTransport session. + // OPTIONAL validate the conn.uri() otherwise call conn.reject() + let session = session + .accept() + .await + .context("failed to accept WebTransport session")?; + + session + .setup() + .versions + .iter() + .find(|v| **v == setup::Version::DRAFT_00) + .context("failed to find supported version")?; + + match session.setup().role { + setup::Role::Subscriber => {} + _ => anyhow::bail!("TODO publishing not yet supported"), + } + + let setup = setup::Server { + version: setup::Version::DRAFT_00, + role: setup::Role::Publisher, + }; + + let (transport, control) = session.accept(setup).await?; + + let session = Self { + transport: Arc::new(transport), + control, + media, + tasks: JoinSet::new(), + }; + + Ok(session) + } + + pub async fn serve(mut self) -> anyhow::Result<()> { + // TODO fix lazy: make a copy of the strings to avoid the borrow checker on self. + let broadcasts: Vec = self.media.keys().cloned().collect(); + + // Announce each available broadcast immediately. + for name in broadcasts { + self.send_message(control::Announce { + track_namespace: name.clone(), + }) + .await?; + } + + loop { + tokio::select! { + msg = self.control.recv() => { + let msg = msg.context("failed to receive control message")?; + self.handle_message(msg).await?; + }, + res = self.tasks.join_next(), if !self.tasks.is_empty() => { + let res = res.expect("no tasks").expect("task aborted"); + if let Err(err) = res { + log::warn!("failed to serve subscription: {:?}", err); + } + } + } + } + } + + async fn handle_message(&mut self, msg: control::Message) -> anyhow::Result<()> { + log::info!("received message: {:?}", msg); + + // TODO implement publish and subscribe halves of the protocol. + match msg { + control::Message::Announce(_) => anyhow::bail!("ANNOUNCE not supported"), + control::Message::AnnounceOk(ref _ok) => Ok(()), // noop + control::Message::AnnounceError(ref err) => { + anyhow::bail!("received ANNOUNCE_ERROR({:?}): {}", err.code, err.reason) + } + control::Message::Subscribe(ref sub) => self.receive_subscribe(sub).await, + control::Message::SubscribeOk(_) => anyhow::bail!("SUBSCRIBE OK not supported"), + control::Message::SubscribeError(_) => anyhow::bail!("SUBSCRIBE ERROR not supported"), + control::Message::GoAway(_) => anyhow::bail!("goaway not supported"), + } + } + + async fn send_message>(&mut self, msg: T) -> anyhow::Result<()> { + let msg = msg.into(); + log::info!("sending message: {:?}", msg); + self.control.send(msg).await + } + + async fn receive_subscribe(&mut self, sub: &control::Subscribe) -> anyhow::Result<()> { + match self.subscribe(sub) { + Ok(()) => { + self.send_message(control::SubscribeOk { + track_id: sub.track_id, + expires: None, + }) + .await + } + Err(e) => { + self.send_message(control::SubscribeError { + track_id: sub.track_id, + code: 1, + reason: e.to_string(), + }) + .await + } + } + } + + fn subscribe(&mut self, sub: &control::Subscribe) -> anyhow::Result<()> { + let broadcast = self + .media + .get(&sub.track_namespace) + .context("unknown track namespace")?; + + let track = broadcast + .tracks + .get(&sub.track_name) + .context("unknown track name")? + .clone(); + + let sub = Subscription { + track, + track_id: sub.track_id, + transport: self.transport.clone(), + }; + + self.tasks.spawn(async move { sub.serve().await }); + + Ok(()) + } +} + +pub struct Subscription { + transport: Arc, + track_id: u64, + track: media::Track, +} + +impl Subscription { + pub async fn serve(mut self) -> anyhow::Result<()> { + let mut tasks = JoinSet::new(); + let mut done = false; + + loop { + tokio::select! { + // Accept new tracks added to the broadcast. + segment = self.track.segments.next(), if !done => { + match segment { + Some(segment) => { + let group = Group { + segment, + transport: self.transport.clone(), + track_id: self.track_id, + }; + + tasks.spawn(async move { group.serve().await }); + }, + None => done = true, // no more segments in the track + } + }, + // Poll any pending segments until they exit. + res = tasks.join_next(), if !tasks.is_empty() => { + let res = res.expect("no tasks").expect("task aborted"); + res.context("failed serve segment")? + }, + else => return Ok(()), // all segments received and finished serving + } + } + } +} + +struct Group { + transport: Arc, + track_id: u64, + segment: media::Segment, +} + +impl Group { + pub async fn serve(mut self) -> anyhow::Result<()> { + // TODO proper values + let header = moq_transport::data::Header { + track_id: self.track_id, + group_sequence: 0, // TODO + object_sequence: 0, // Always zero since we send an entire group as an object + send_order: 0, // TODO + }; + + let mut stream = self.transport.send(header).await?; + + // Write each fragment as they are available. + while let Some(fragment) = self.segment.fragments.next().await { + stream.write_all(fragment.as_slice()).await?; + } + + // NOTE: stream is automatically closed when dropped + + Ok(()) + } +} diff --git a/src/lib.rs b/moq-warp/src/lib.rs similarity index 100% rename from src/lib.rs rename to moq-warp/src/lib.rs diff --git a/src/main.rs b/moq-warp/src/main.rs similarity index 92% rename from src/main.rs rename to moq-warp/src/main.rs index 7aaa38e..123e946 100644 --- a/src/main.rs +++ b/moq-warp/src/main.rs @@ -1,6 +1,9 @@ -use moq::{app, media}; +use moq_warp::{app, media}; use std::{fs, io, net, path, sync}; +use std::collections::HashMap; +use std::sync::Arc; + use anyhow::Context; use clap::Parser; use ring::digest::{digest, SHA256}; @@ -38,12 +41,15 @@ async fn main() -> anyhow::Result<()> { // Create a fake media source from disk. let mut media = media::Source::new(args.media).context("failed to open fragmented.mp4")?; + let mut broadcasts = HashMap::new(); + broadcasts.insert("demo".to_string(), media.broadcast()); + // Create a server to actually serve the media let config = app::ServerConfig { addr: args.addr, cert: args.cert, key: args.key, - broadcast: media.broadcast(), + broadcasts: Arc::new(broadcasts), }; let mut server = app::Server::new(config).context("failed to create server")?; diff --git a/src/media/mod.rs b/moq-warp/src/media/mod.rs similarity index 100% rename from src/media/mod.rs rename to moq-warp/src/media/mod.rs diff --git a/src/media/model.rs b/moq-warp/src/media/model.rs similarity index 57% rename from src/media/model.rs rename to moq-warp/src/media/model.rs index bab22bd..0bdac2e 100644 --- a/src/media/model.rs +++ b/moq-warp/src/media/model.rs @@ -1,16 +1,20 @@ use super::Subscriber; -use std::{sync, time}; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; + +// Map from track namespace to broadcast. +// TODO support updates +pub type Broadcasts = Arc>; #[derive(Clone)] pub struct Broadcast { - pub tracks: Subscriber, + // TODO support updates. + pub tracks: Arc>, } #[derive(Clone)] pub struct Track { - // The track ID as stored in the MP4 - pub id: u32, - // A list of segments, which are independently decodable. pub segments: Subscriber, } @@ -18,11 +22,11 @@ pub struct Track { #[derive(Clone)] pub struct Segment { // The timestamp of the segment. - pub timestamp: time::Duration, + pub timestamp: Duration, // A list of fragments that make up the segment. pub fragments: Subscriber, } // Use Arc to avoid cloning the entire MP4 data for each subscriber. -pub type Fragment = sync::Arc>; +pub type Fragment = Arc>; diff --git a/src/media/source.rs b/moq-warp/src/media/source.rs similarity index 94% rename from src/media/source.rs rename to moq-warp/src/media/source.rs index 6910c06..22cf04d 100644 --- a/src/media/source.rs +++ b/moq-warp/src/media/source.rs @@ -9,6 +9,7 @@ use mp4::ReadBox; use anyhow::Context; use std::collections::HashMap; +use std::sync::Arc; use super::{Broadcast, Fragment, Producer, Segment, Track}; @@ -16,7 +17,7 @@ pub struct Source { // We read the file once, in order, and don't seek backwards. reader: io::BufReader, - // The tracks we're producing + // The subscribable broadcast. broadcast: Broadcast, // The tracks we're producing. @@ -45,20 +46,15 @@ impl Source { // Parse the moov box so we can detect the timescales for each track. let moov = mp4::MoovBox::read_box(&mut moov_reader, moov_header.size)?; - // Create a producer to populate the tracks. - let mut tracks = Producer::::new(); - - let broadcast = Broadcast { - tracks: tracks.subscribe(), - }; + let mut tracks = HashMap::new(); // Create the init track let init_track = Self::create_init_track(init); - tracks.push(init_track); + tracks.insert("catalog".to_string(), init_track); // Create a map with the current segment for each track. // NOTE: We don't add the init track to this, since it's not part of the MP4. - let mut lookup = HashMap::new(); + let mut sources = HashMap::new(); for trak in &moov.traks { let track_id = trak.tkhd.track_id; @@ -68,20 +64,29 @@ impl Source { let segments = Producer::::new(); - tracks.push(Track { - id: track_id, - segments: segments.subscribe(), - }); + // Insert the subscribable track for consumerts. + // The track_name is just the integer track ID. + let track_name = track_id.to_string(); + tracks.insert( + track_name, + Track { + segments: segments.subscribe(), + }, + ); // Store the track publisher in a map so we can update it later. - let track = SourceTrack::new(segments, timescale); - lookup.insert(track_id, track); + let source = SourceTrack::new(segments, timescale); + sources.insert(track_id, source); } + let broadcast = Broadcast { + tracks: Arc::new(tracks), + }; + Ok(Self { reader, broadcast, - tracks: lookup, + tracks: sources, }) } @@ -98,7 +103,6 @@ impl Source { }); Track { - id: 0xff, segments: segments.subscribe(), } } diff --git a/src/media/watch.rs b/moq-warp/src/media/watch.rs similarity index 98% rename from src/media/watch.rs rename to moq-warp/src/media/watch.rs index b164b7e..a68ef7a 100644 --- a/src/media/watch.rs +++ b/moq-warp/src/media/watch.rs @@ -121,7 +121,7 @@ impl Subscriber { // Return None if we've consumed all entries and the queue is closed. None } else { - panic!("impossible subscriber state") + unreachable!("impossible subscriber state") } } } diff --git a/src/app/message.rs b/src/app/message.rs deleted file mode 100644 index d8ea6b8..0000000 --- a/src/app/message.rs +++ /dev/null @@ -1,34 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Default)] -pub struct Message { - pub init: Option, - pub segment: Option, -} - -#[derive(Serialize, Deserialize)] -pub struct Init {} - -#[derive(Serialize, Deserialize)] -pub struct Segment { - pub track_id: u32, -} - -impl Message { - pub fn new() -> Self { - Default::default() - } - - pub fn serialize(&self) -> anyhow::Result> { - let str = serde_json::to_string(self)?; - let bytes = str.as_bytes(); - let size = bytes.len() + 8; - - let mut out = Vec::with_capacity(size); - out.extend_from_slice(&(size as u32).to_be_bytes()); - out.extend_from_slice(b"warp"); - out.extend_from_slice(bytes); - - Ok(out) - } -} diff --git a/src/app/mod.rs b/src/app/mod.rs deleted file mode 100644 index 07f563a..0000000 --- a/src/app/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod message; -mod server; -mod session; - -pub use server::{Server, ServerConfig}; - -// Reduce the amount of typing -type WebTransportSession = h3_webtransport::server::WebTransportSession; diff --git a/src/app/session.rs b/src/app/session.rs deleted file mode 100644 index 08ecdb7..0000000 --- a/src/app/session.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::media; - -use anyhow::Context; - -use std::sync::Arc; -use tokio::io::AsyncWriteExt; -use tokio::task::JoinSet; - -use super::WebTransportSession; - -use super::message; - -#[derive(Clone)] -pub struct Session { - // The underlying transport session - transport: Arc, -} - -impl Session { - pub fn new(transport: WebTransportSession) -> Self { - let transport = Arc::new(transport); - Self { transport } - } - - pub async fn serve_broadcast(&self, mut broadcast: media::Broadcast) -> anyhow::Result<()> { - let mut tasks = JoinSet::new(); - let mut done = false; - - loop { - tokio::select! { - // Accept new tracks added to the broadcast. - track = broadcast.tracks.next(), if !done => { - match track { - Some(track) => { - let session = self.clone(); - - tasks.spawn(async move { - session.serve_track(track).await - }); - }, - None => done = true, - } - }, - // Poll any pending tracks until they exit. - res = tasks.join_next(), if !tasks.is_empty() => { - let res = res.context("no tracks running")?; - let res = res.context("failed to run track")?; - res.context("failed to serve track")?; - }, - else => return Ok(()), - } - } - } - - pub async fn serve_track(&self, mut track: media::Track) -> anyhow::Result<()> { - let mut tasks = JoinSet::new(); - let mut done = false; - - loop { - tokio::select! { - // Accept new tracks added to the broadcast. - segment = track.segments.next(), if !done => { - match segment { - Some(segment) => { - let track = track.clone(); - let session = self.clone(); - - tasks.spawn(async move { - session.serve_segment(track, segment).await - }); - }, - None => done = true, - } - }, - // Poll any pending segments until they exit. - res = tasks.join_next(), if !tasks.is_empty() => { - let res = res.context("no tasks running")?; - let res = res.context("failed to run segment")?; - res.context("failed serve segment")? - }, - else => return Ok(()), - } - } - } - - pub async fn serve_segment(&self, track: media::Track, mut segment: media::Segment) -> anyhow::Result<()> { - let mut stream = self.transport.open_uni(self.transport.session_id()).await?; - - // TODO support prioirty - // stream.set_priority(0); - - // Encode a JSON header indicating this is a new segment. - let mut message: message::Message = message::Message::new(); - - // TODO combine init and segment messages into one. - if track.id == 0xff { - message.init = Some(message::Init {}); - } else { - message.segment = Some(message::Segment { track_id: track.id }); - } - - // Write the JSON header. - let data = message.serialize()?; - stream.write_all(data.as_slice()).await?; - - // Write each fragment as they are available. - while let Some(fragment) = segment.fragments.next().await { - stream.write_all(fragment.as_slice()).await?; - } - - // NOTE: stream is automatically closed when dropped - - Ok(()) - } -}