From 2601c40b54ec0e40c7651a4e870e1f3f65854fbe Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 13 Apr 2023 10:20:17 -0700 Subject: [PATCH] Replace Go with Rust. No code written yet. --- server/.gitignore | 2 +- server/Cargo.lock | 279 +++++++++++++++++++++++ server/Cargo.toml | 9 + server/go.mod | 30 --- server/go.sum | 264 ---------------------- server/internal/warp/media.go | 380 -------------------------------- server/internal/warp/message.go | 20 -- server/internal/warp/server.go | 132 ----------- server/internal/warp/session.go | 279 ----------------------- server/internal/warp/stream.go | 144 ------------ server/internal/web/web.go | 64 ------ server/main.go | 71 ------ server/src/main.rs | 3 + 13 files changed, 292 insertions(+), 1385 deletions(-) create mode 100644 server/Cargo.lock create mode 100644 server/Cargo.toml delete mode 100644 server/go.mod delete mode 100644 server/go.sum delete mode 100644 server/internal/warp/media.go delete mode 100644 server/internal/warp/message.go delete mode 100644 server/internal/warp/server.go delete mode 100644 server/internal/warp/session.go delete mode 100644 server/internal/warp/stream.go delete mode 100644 server/internal/web/web.go delete mode 100644 server/main.go create mode 100644 server/src/main.rs diff --git a/server/.gitignore b/server/.gitignore index 333c1e9..eb5a316 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1 +1 @@ -logs/ +target diff --git a/server/Cargo.lock b/server/Cargo.lock new file mode 100644 index 0000000..6110308 --- /dev/null +++ b/server/Cargo.lock @@ -0,0 +1,279 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[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 = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" + +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "octets" +version = "0.2.0" +source = "git+https://github.com/n8o/quiche.git?branch=master#0137dc3ca6f4f31e3175d0a0868acb9c64b46cc7" + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quiche" +version = "0.17.1" +source = "git+https://github.com/n8o/quiche.git?branch=master#0137dc3ca6f4f31e3175d0a0868acb9c64b46cc7" +dependencies = [ + "cmake", + "lazy_static", + "libc", + "libm", + "log", + "octets", + "ring", + "slab", + "smallvec", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[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", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "serde" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" + +[[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" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "warp-server" +version = "0.1.0" +dependencies = [ + "quiche", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..d4dcbb7 --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "warp-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +quiche = { git = "https://github.com/n8o/quiche.git", branch = "master" } # WebTransport fork diff --git a/server/go.mod b/server/go.mod deleted file mode 100644 index 7e5b4fb..0000000 --- a/server/go.mod +++ /dev/null @@ -1,30 +0,0 @@ -module github.com/kixelated/warp/server - -go 1.18 - -require ( - github.com/abema/go-mp4 v0.7.2 - github.com/kixelated/invoker v1.0.0 - github.com/kixelated/quic-go v1.31.0 - github.com/kixelated/webtransport-go v1.4.1 - github.com/zencoder/go-dash/v3 v3.0.2 -) - -require ( - github.com/francoispqt/gojay v1.2.13 // indirect - github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect - github.com/google/uuid v1.1.2 // indirect - github.com/marten-seemann/qpack v0.3.0 // indirect - github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect - github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect - github.com/onsi/ginkgo/v2 v2.2.0 // indirect - golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect - golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06 // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.12 // indirect -) diff --git a/server/go.sum b/server/go.sum deleted file mode 100644 index fed92be..0000000 --- a/server/go.sum +++ /dev/null @@ -1,264 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= -dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= -dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= -dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= -dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= -git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/abema/go-mp4 v0.7.2 h1:ugTC8gfEmjyaDKpXs3vi2QzgJbDu9B8m6UMMIpbYbGg= -github.com/abema/go-mp4 v0.7.2/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= -github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= -github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kisielk/errcheck v1.4.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kixelated/invoker v1.0.0 h1:0wYlvK39yQPbkwIFy+YN41AhF89WOtGyWqV2pZB39xw= -github.com/kixelated/invoker v1.0.0/go.mod h1:RjG3iqm/sKwZjOpcW4SGq+l+4DJCDR/yUtc70VjCRB8= -github.com/kixelated/quic-go v1.31.0 h1:p2vq3Otvtmz+0EP23vjumnO/HU4Q/DFxNF6xNryVfmA= -github.com/kixelated/quic-go v1.31.0/go.mod h1:AO7pURnb8HXHmdalp5e09UxQfsuwseEhl0NLmwiSOFY= -github.com/kixelated/webtransport-go v1.4.1 h1:ZtY3P7hVe1wK5fAt71b+HHnNISFDcQ913v+bvaNATxA= -github.com/kixelated/webtransport-go v1.4.1/go.mod h1:6RV5pTXF7oP53T83bosSDsLdSdw31j5cfpMDqsO4D5k= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE= -github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g= -github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI= -github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= -github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE= -github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= -github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= -github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= -github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw= -github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= -github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= -github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= -github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= -github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= -github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= -github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= -github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= -github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= -github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= -github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= -github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= -github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= -github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= -github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= -github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= -github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= -github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= -github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/zencoder/go-dash/v3 v3.0.2 h1:oP1+dOh+Gp57PkvdCyMfbHtrHaxfl3w4kR3KBBbuqQE= -github.com/zencoder/go-dash/v3 v3.0.2/go.mod h1:30R5bKy1aUYY45yesjtZ9l8trNc2TwNqbS17WVQmCzk= -go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= -golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06 h1:E1pm64FqQa4v8dHd/bAneyMkR4hk8LTJhoSlc5mc1cM= -golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= -google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= -gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= -sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/server/internal/warp/media.go b/server/internal/warp/media.go deleted file mode 100644 index 86d6f93..0000000 --- a/server/internal/warp/media.go +++ /dev/null @@ -1,380 +0,0 @@ -package warp - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - "io" - "io/fs" - "os" - "path/filepath" - "strings" - "time" - - "github.com/abema/go-mp4" - "github.com/kixelated/invoker" - "github.com/zencoder/go-dash/v3/mpd" -) - -// This is a demo; you should actually fetch media from a live backend. -// It's just much easier to read from disk and "fake" being live. -type Media struct { - base fs.FS - inits map[string]*MediaInit - video []*mpd.Representation - audio []*mpd.Representation -} - -func NewMedia(playlistPath string) (m *Media, err error) { - m = new(Media) - - // Create a fs.FS out of the folder holding the playlist - m.base = os.DirFS(filepath.Dir(playlistPath)) - - // Read the playlist file - playlist, err := mpd.ReadFromFile(playlistPath) - if err != nil { - return nil, fmt.Errorf("failed to open playlist: %w", err) - } - - if len(playlist.Periods) > 1 { - return nil, fmt.Errorf("multiple periods not supported") - } - - period := playlist.Periods[0] - - for _, adaption := range period.AdaptationSets { - representation := adaption.Representations[0] - - if representation.MimeType == nil { - return nil, fmt.Errorf("missing representation mime type") - } - - if representation.Bandwidth == nil { - return nil, fmt.Errorf("missing representation bandwidth") - } - - switch *representation.MimeType { - case "video/mp4": - m.video = append(m.video, representation) - case "audio/mp4": - m.audio = append(m.audio, representation) - } - } - - if len(m.video) == 0 { - return nil, fmt.Errorf("no video representation found") - } - - if len(m.audio) == 0 { - return nil, fmt.Errorf("no audio representation found") - } - - m.inits = make(map[string]*MediaInit) - - var reps []*mpd.Representation - reps = append(reps, m.audio...) - reps = append(reps, m.video...) - - for _, rep := range reps { - path := *rep.SegmentTemplate.Initialization - - // TODO Support the full template engine - path = strings.ReplaceAll(path, "$RepresentationID$", *rep.ID) - - f, err := fs.ReadFile(m.base, path) - if err != nil { - return nil, fmt.Errorf("failed to read init file: %w", err) - } - - init, err := newMediaInit(*rep.ID, f) - if err != nil { - return nil, fmt.Errorf("failed to create init segment: %w", err) - } - - m.inits[*rep.ID] = init - } - - return m, nil -} - -func (m *Media) Start(bitrate func() uint64) (inits map[string]*MediaInit, audio *MediaStream, video *MediaStream, err error) { - start := time.Now() - - audio, err = newMediaStream(m, m.audio, start, bitrate) - if err != nil { - return nil, nil, nil, err - } - - video, err = newMediaStream(m, m.video, start, bitrate) - if err != nil { - return nil, nil, nil, err - } - - return m.inits, audio, video, nil -} - -type MediaStream struct { - Media *Media - - start time.Time - reps []*mpd.Representation - sequence int - bitrate func() uint64 // returns the current estimated bitrate -} - -func newMediaStream(m *Media, reps []*mpd.Representation, start time.Time, bitrate func() uint64) (ms *MediaStream, err error) { - ms = new(MediaStream) - ms.Media = m - ms.reps = reps - ms.start = start - ms.bitrate = bitrate - return ms, nil -} - -func (ms *MediaStream) chooseRepresentation() (choice *mpd.Representation) { - bitrate := ms.bitrate() - - // Loop over the renditions and pick the highest bitrate we can support - for _, r := range ms.reps { - if uint64(*r.Bandwidth) <= bitrate && (choice == nil || *r.Bandwidth > *choice.Bandwidth) { - choice = r - } - } - - if choice != nil { - return choice - } - - // We can't support any of the bitrates, so find the lowest one. - for _, r := range ms.reps { - if choice == nil || *r.Bandwidth < *choice.Bandwidth { - choice = r - } - } - - return choice -} - -// Returns the next segment in the stream -func (ms *MediaStream) Next(ctx context.Context) (segment *MediaSegment, err error) { - rep := ms.chooseRepresentation() - - if rep.SegmentTemplate == nil { - return nil, fmt.Errorf("missing segment template") - } - - if rep.SegmentTemplate.Media == nil { - return nil, fmt.Errorf("no media template") - } - - if rep.SegmentTemplate.StartNumber == nil { - return nil, fmt.Errorf("missing start number") - } - - path := *rep.SegmentTemplate.Media - sequence := ms.sequence + int(*rep.SegmentTemplate.StartNumber) - - // TODO Support the full template engine - path = strings.ReplaceAll(path, "$RepresentationID$", *rep.ID) - path = strings.ReplaceAll(path, "$Number%05d$", fmt.Sprintf("%05d", sequence)) // TODO TODO - - // Try openning the file - f, err := ms.Media.base.Open(path) - if errors.Is(err, os.ErrNotExist) && ms.sequence != 0 { - // Return EOF if the next file is missing - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("failed to open segment file: %w", err) - } - - duration := time.Duration(*rep.SegmentTemplate.Duration) / time.Nanosecond - timestamp := time.Duration(ms.sequence) * duration - - init := ms.Media.inits[*rep.ID] - - segment, err = newMediaSegment(ms, init, f, timestamp) - if err != nil { - return nil, fmt.Errorf("failed to create segment: %w", err) - } - - ms.sequence += 1 - - return segment, nil -} - -type MediaInit struct { - ID string - Raw []byte - Timescale int -} - -func newMediaInit(id string, raw []byte) (mi *MediaInit, err error) { - mi = new(MediaInit) - mi.ID = id - mi.Raw = raw - - err = mi.parse() - if err != nil { - return nil, fmt.Errorf("failed to parse init segment: %w", err) - } - - return mi, nil -} - -// Parse through the init segment, literally just to populate the timescale -func (mi *MediaInit) parse() (err error) { - r := bytes.NewReader(mi.Raw) - - _, err = mp4.ReadBoxStructure(r, func(h *mp4.ReadHandle) (interface{}, error) { - if !h.BoxInfo.IsSupportedType() { - return nil, nil - } - - payload, _, err := h.ReadPayload() - if err != nil { - return nil, err - } - - switch box := payload.(type) { - case *mp4.Mdhd: // Media Header; moov -> trak -> mdia > mdhd - if mi.Timescale != 0 { - // verify only one track - return nil, fmt.Errorf("multiple mdhd atoms") - } - - mi.Timescale = int(box.Timescale) - } - - // Expands children - return h.Expand() - }) - - if err != nil { - return fmt.Errorf("failed to parse MP4 file: %w", err) - } - - return nil -} - -type MediaSegment struct { - Stream *MediaStream - Init *MediaInit - - file fs.File - timestamp time.Duration -} - -func newMediaSegment(s *MediaStream, init *MediaInit, file fs.File, timestamp time.Duration) (ms *MediaSegment, err error) { - ms = new(MediaSegment) - ms.Stream = s - ms.Init = init - - ms.file = file - ms.timestamp = timestamp - - return ms, nil -} - -// Return the next atom, sleeping based on the PTS to simulate a live stream -func (ms *MediaSegment) Read(ctx context.Context) (chunk []byte, err error) { - // Read the next top-level box - var header [8]byte - - _, err = io.ReadFull(ms.file, header[:]) - if err != nil { - return nil, fmt.Errorf("failed to read header: %w", err) - } - - size := int(binary.BigEndian.Uint32(header[0:4])) - if size < 8 { - return nil, fmt.Errorf("box is too small") - } - - buf := make([]byte, size) - n := copy(buf, header[:]) - - _, err = io.ReadFull(ms.file, buf[n:]) - if err != nil { - return nil, fmt.Errorf("failed to read atom: %w", err) - } - - sample, err := ms.parseAtom(ctx, buf) - if err != nil { - return nil, fmt.Errorf("failed to parse atom: %w", err) - } - - if sample != nil { - // Simulate a live stream by sleeping before we write this sample. - // Figure out how much time has elapsed since the start - elapsed := time.Since(ms.Stream.start) - delay := sample.Timestamp - elapsed - - if delay > 0 { - // Sleep until we're supposed to see these samples - err = invoker.Sleep(delay)(ctx) - if err != nil { - return nil, err - } - } - } - - return buf, nil -} - -// Parse through the MP4 atom, returning infomation about the next fragmented sample -func (ms *MediaSegment) parseAtom(ctx context.Context, buf []byte) (sample *mediaSample, err error) { - r := bytes.NewReader(buf) - - _, err = mp4.ReadBoxStructure(r, func(h *mp4.ReadHandle) (interface{}, error) { - if !h.BoxInfo.IsSupportedType() { - return nil, nil - } - - payload, _, err := h.ReadPayload() - if err != nil { - return nil, err - } - - switch box := payload.(type) { - case *mp4.Moof: - sample = new(mediaSample) - case *mp4.Tfdt: // Track Fragment Decode Timestamp; moof -> traf -> tfdt - // TODO This box isn't required - // TODO we want the last PTS if there are multiple samples - var dts time.Duration - if box.FullBox.Version == 0 { - dts = time.Duration(box.BaseMediaDecodeTimeV0) - } else { - dts = time.Duration(box.BaseMediaDecodeTimeV1) - } - - if ms.Init.Timescale == 0 { - return nil, fmt.Errorf("missing timescale") - } - - // Convert to seconds - // TODO What about PTS? - sample.Timestamp = dts * time.Second / time.Duration(ms.Init.Timescale) - } - - // Expands children - return h.Expand() - }) - - if err != nil { - return nil, fmt.Errorf("failed to parse MP4 file: %w", err) - } - - return sample, nil -} - -func (ms *MediaSegment) Close() (err error) { - return ms.file.Close() -} - -type mediaSample struct { - Timestamp time.Duration // The timestamp of the first sample -} diff --git a/server/internal/warp/message.go b/server/internal/warp/message.go deleted file mode 100644 index 5514b78..0000000 --- a/server/internal/warp/message.go +++ /dev/null @@ -1,20 +0,0 @@ -package warp - -type Message struct { - Init *MessageInit `json:"init,omitempty"` - Segment *MessageSegment `json:"segment,omitempty"` - Debug *MessageDebug `json:"debug,omitempty"` -} - -type MessageInit struct { - Id string `json:"id"` // ID of the init segment -} - -type MessageSegment struct { - Init string `json:"init"` // ID of the init segment to use for this segment - Timestamp int `json:"timestamp"` // PTS of the first frame in milliseconds -} - -type MessageDebug struct { - MaxBitrate int `json:"max_bitrate"` // Artificially limit the QUIC max bitrate -} diff --git a/server/internal/warp/server.go b/server/internal/warp/server.go deleted file mode 100644 index 9ad6f18..0000000 --- a/server/internal/warp/server.go +++ /dev/null @@ -1,132 +0,0 @@ -package warp - -import ( - "context" - "crypto/tls" - "encoding/hex" - "fmt" - "io" - "log" - "net/http" - "os" - "path/filepath" - - "github.com/kixelated/invoker" - "github.com/kixelated/quic-go" - "github.com/kixelated/quic-go/http3" - "github.com/kixelated/quic-go/logging" - "github.com/kixelated/quic-go/qlog" - "github.com/kixelated/webtransport-go" -) - -type Server struct { - inner *webtransport.Server - media *Media - sessions invoker.Tasks - cert *tls.Certificate -} - -type Config struct { - Addr string - Cert *tls.Certificate - LogDir string - Media *Media -} - -func New(config Config) (s *Server, err error) { - s = new(Server) - s.cert = config.Cert - s.media = config.Media - - quicConfig := &quic.Config{} - - if config.LogDir != "" { - quicConfig.Tracer = qlog.NewTracer(func(p logging.Perspective, connectionID []byte) io.WriteCloser { - path := fmt.Sprintf("%s-%s.qlog", p, hex.EncodeToString(connectionID)) - - f, err := os.Create(filepath.Join(config.LogDir, path)) - if err != nil { - // lame - panic(err) - } - - return f - }) - } - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{*s.cert}, - } - - // Host a HTTP/3 server to serve the WebTransport endpoint - mux := http.NewServeMux() - mux.HandleFunc("/watch", s.handleWatch) - - s.inner = &webtransport.Server{ - H3: http3.Server{ - TLSConfig: tlsConfig, - QuicConfig: quicConfig, - Addr: config.Addr, - Handler: mux, - }, - CheckOrigin: func(r *http.Request) bool { return true }, - } - - return s, nil -} - -func (s *Server) runServe(ctx context.Context) (err error) { - return s.inner.ListenAndServe() -} - -func (s *Server) runShutdown(ctx context.Context) (err error) { - <-ctx.Done() - s.inner.Close() // close on context shutdown - return ctx.Err() -} - -func (s *Server) Run(ctx context.Context) (err error) { - return invoker.Run(ctx, s.runServe, s.runShutdown, s.sessions.Repeat) -} - -func (s *Server) handleWatch(w http.ResponseWriter, r *http.Request) { - hijacker, ok := w.(http3.Hijacker) - if !ok { - panic("unable to hijack connection: must use kixelated/quic-go") - } - - conn := hijacker.Connection() - - sess, err := s.inner.Upgrade(w, r) - if err != nil { - http.Error(w, "failed to upgrade session", 500) - return - } - - err = s.serveSession(r.Context(), conn, sess) - if err != nil { - log.Println(err) - } -} - -func (s *Server) serveSession(ctx context.Context, conn quic.Connection, sess *webtransport.Session) (err error) { - defer func() { - if err != nil { - sess.CloseWithError(1, err.Error()) - } else { - sess.CloseWithError(0, "end of broadcast") - } - }() - - ss, err := NewSession(conn, sess, s.media) - if err != nil { - return fmt.Errorf("failed to create session: %w", err) - } - - err = ss.Run(ctx) - if err != nil { - return fmt.Errorf("terminated session: %w", err) - } - - return nil -} diff --git a/server/internal/warp/session.go b/server/internal/warp/session.go deleted file mode 100644 index a267619..0000000 --- a/server/internal/warp/session.go +++ /dev/null @@ -1,279 +0,0 @@ -package warp - -import ( - "context" - "encoding/binary" - "encoding/json" - "errors" - "fmt" - "io" - "log" - "math" - "time" - - "github.com/kixelated/invoker" - "github.com/kixelated/quic-go" - "github.com/kixelated/webtransport-go" -) - -// A single WebTransport session -type Session struct { - conn quic.Connection - inner *webtransport.Session - - media *Media - inits map[string]*MediaInit - audio *MediaStream - video *MediaStream - - streams invoker.Tasks -} - -func NewSession(connection quic.Connection, session *webtransport.Session, media *Media) (s *Session, err error) { - s = new(Session) - s.conn = connection - s.inner = session - s.media = media - return s, nil -} - -func (s *Session) Run(ctx context.Context) (err error) { - s.inits, s.audio, s.video, err = s.media.Start(s.conn.GetMaxBandwidth) - if err != nil { - return fmt.Errorf("failed to start media: %w", err) - } - - // Once we've validated the session, now we can start accessing the streams - return invoker.Run(ctx, s.runAccept, s.runAcceptUni, s.runInit, s.runAudio, s.runVideo, s.streams.Repeat) -} - -func (s *Session) runAccept(ctx context.Context) (err error) { - for { - stream, err := s.inner.AcceptStream(ctx) - if err != nil { - return fmt.Errorf("failed to accept bidirectional stream: %w", err) - } - - // Warp doesn't utilize bidirectional streams so just close them immediately. - // We might use them in the future so don't close the connection with an error. - stream.CancelRead(1) - } -} - -func (s *Session) runAcceptUni(ctx context.Context) (err error) { - for { - stream, err := s.inner.AcceptUniStream(ctx) - if err != nil { - return fmt.Errorf("failed to accept unidirectional stream: %w", err) - } - - s.streams.Add(func(ctx context.Context) (err error) { - return s.handleStream(ctx, stream) - }) - } -} - -func (s *Session) handleStream(ctx context.Context, stream webtransport.ReceiveStream) (err error) { - defer func() { - if err != nil { - stream.CancelRead(1) - } - }() - - var header [8]byte - for { - _, err = io.ReadFull(stream, header[:]) - if errors.Is(io.EOF, err) { - return nil - } else if err != nil { - return fmt.Errorf("failed to read atom header: %w", err) - } - - size := binary.BigEndian.Uint32(header[0:4]) - name := string(header[4:8]) - - if size < 8 { - return fmt.Errorf("atom size is too small") - } else if size > 42069 { // arbitrary limit - return fmt.Errorf("atom size is too large") - } else if name != "warp" { - return fmt.Errorf("only warp atoms are supported") - } - - payload := make([]byte, size-8) - - _, err = io.ReadFull(stream, payload) - if err != nil { - return fmt.Errorf("failed to read atom payload: %w", err) - } - - log.Println("received message:", string(payload)) - - msg := Message{} - - err = json.Unmarshal(payload, &msg) - if err != nil { - return fmt.Errorf("failed to decode json payload: %w", err) - } - - if msg.Debug != nil { - s.setDebug(msg.Debug) - } - } -} - -func (s *Session) runInit(ctx context.Context) (err error) { - for _, init := range s.inits { - err = s.writeInit(ctx, init) - if err != nil { - return fmt.Errorf("failed to write init stream: %w", err) - } - } - - return nil -} - -func (s *Session) runAudio(ctx context.Context) (err error) { - for { - segment, err := s.audio.Next(ctx) - if err != nil { - return fmt.Errorf("failed to get next segment: %w", err) - } - - if segment == nil { - return nil - } - - err = s.writeSegment(ctx, segment) - if err != nil { - return fmt.Errorf("failed to write segment stream: %w", err) - } - } -} - -func (s *Session) runVideo(ctx context.Context) (err error) { - for { - segment, err := s.video.Next(ctx) - if err != nil { - return fmt.Errorf("failed to get next segment: %w", err) - } - - if segment == nil { - return nil - } - - err = s.writeSegment(ctx, segment) - if err != nil { - return fmt.Errorf("failed to write segment stream: %w", err) - } - } -} - -// Create a stream for an INIT segment and write the container. -func (s *Session) writeInit(ctx context.Context, init *MediaInit) (err error) { - temp, err := s.inner.OpenUniStreamSync(ctx) - if err != nil { - return fmt.Errorf("failed to create stream: %w", err) - } - - if temp == nil { - // Not sure when this happens, perhaps when closing a connection? - return fmt.Errorf("received a nil stream from quic-go") - } - - // Wrap the stream in an object that buffers writes instead of blocking. - stream := NewStream(temp) - s.streams.Add(stream.Run) - - defer func() { - if err != nil { - stream.WriteCancel(1) - } - }() - - stream.SetPriority(math.MaxInt) - - err = stream.WriteMessage(Message{ - Init: &MessageInit{Id: init.ID}, - }) - if err != nil { - return fmt.Errorf("failed to write init header: %w", err) - } - - _, err = stream.Write(init.Raw) - if err != nil { - return fmt.Errorf("failed to write init data: %w", err) - } - - err = stream.Close() - if err != nil { - return fmt.Errorf("failed to close init stream: %w", err) - } - - return nil -} - -// Create a stream for a segment and write the contents, chunk by chunk. -func (s *Session) writeSegment(ctx context.Context, segment *MediaSegment) (err error) { - temp, err := s.inner.OpenUniStreamSync(ctx) - if err != nil { - return fmt.Errorf("failed to create stream: %w", err) - } - - if temp == nil { - // Not sure when this happens, perhaps when closing a connection? - return fmt.Errorf("received a nil stream from quic-go") - } - - // Wrap the stream in an object that buffers writes instead of blocking. - stream := NewStream(temp) - s.streams.Add(stream.Run) - - defer func() { - if err != nil { - stream.WriteCancel(1) - } - }() - - ms := int(segment.timestamp / time.Millisecond) - - // newer segments take priority - stream.SetPriority(ms) - - err = stream.WriteMessage(Message{ - Segment: &MessageSegment{ - Init: segment.Init.ID, - Timestamp: ms, - }, - }) - if err != nil { - return fmt.Errorf("failed to write segment header: %w", err) - } - - for { - // Get the next fragment - buf, err := segment.Read(ctx) - if errors.Is(err, io.EOF) { - break - } else if err != nil { - return fmt.Errorf("failed to read segment data: %w", err) - } - - // NOTE: This won't block because of our wrapper - _, err = stream.Write(buf) - if err != nil { - return fmt.Errorf("failed to write segment data: %w", err) - } - } - - err = stream.Close() - if err != nil { - return fmt.Errorf("failed to close segemnt stream: %w", err) - } - - return nil -} - -func (s *Session) setDebug(msg *MessageDebug) { - s.conn.SetMaxBandwidth(uint64(msg.MaxBitrate)) -} diff --git a/server/internal/warp/stream.go b/server/internal/warp/stream.go deleted file mode 100644 index 2ba56b6..0000000 --- a/server/internal/warp/stream.go +++ /dev/null @@ -1,144 +0,0 @@ -package warp - -import ( - "context" - "encoding/binary" - "encoding/json" - "fmt" - "sync" - - "github.com/kixelated/webtransport-go" -) - -// Wrapper around quic.SendStream to make Write non-blocking. -// Otherwise we can't write to multiple concurrent streams in the same goroutine. -type Stream struct { - inner webtransport.SendStream - - chunks [][]byte - closed bool - err error - - notify chan struct{} - mutex sync.Mutex -} - -func NewStream(inner webtransport.SendStream) (s *Stream) { - s = new(Stream) - s.inner = inner - s.notify = make(chan struct{}) - return s -} - -func (s *Stream) Run(ctx context.Context) (err error) { - defer func() { - s.mutex.Lock() - s.err = err - s.mutex.Unlock() - }() - - for { - s.mutex.Lock() - - chunks := s.chunks - notify := s.notify - closed := s.closed - - s.chunks = s.chunks[len(s.chunks):] - s.mutex.Unlock() - - for _, chunk := range chunks { - _, err = s.inner.Write(chunk) - if err != nil { - return err - } - } - - if closed { - return s.inner.Close() - } - - if len(chunks) == 0 { - select { - case <-ctx.Done(): - return ctx.Err() - case <-notify: - } - } - } -} - -func (s *Stream) Write(buf []byte) (n int, err error) { - s.mutex.Lock() - defer s.mutex.Unlock() - - if s.err != nil { - return 0, s.err - } - - if s.closed { - return 0, fmt.Errorf("closed") - } - - // Make a copy of the buffer so it's long lived - buf = append([]byte{}, buf...) - s.chunks = append(s.chunks, buf) - - // Wake up the writer - close(s.notify) - s.notify = make(chan struct{}) - - return len(buf), nil -} - -func (s *Stream) WriteMessage(msg Message) (err error) { - payload, err := json.Marshal(msg) - if err != nil { - return fmt.Errorf("failed to marshal message: %w", err) - } - - var size [4]byte - binary.BigEndian.PutUint32(size[:], uint32(len(payload)+8)) - - _, err = s.Write(size[:]) - if err != nil { - return fmt.Errorf("failed to write size: %w", err) - } - - _, err = s.Write([]byte("warp")) - if err != nil { - return fmt.Errorf("failed to write atom header: %w", err) - } - - _, err = s.Write(payload) - if err != nil { - return fmt.Errorf("failed to write payload: %w", err) - } - - return nil -} - -func (s *Stream) WriteCancel(code webtransport.StreamErrorCode) { - s.inner.CancelWrite(code) -} - -func (s *Stream) SetPriority(prio int) { - s.inner.SetPriority(prio) -} - -func (s *Stream) Close() (err error) { - s.mutex.Lock() - defer s.mutex.Unlock() - - if s.err != nil { - return s.err - } - - s.closed = true - - // Wake up the writer - close(s.notify) - s.notify = make(chan struct{}) - - return nil -} diff --git a/server/internal/web/web.go b/server/internal/web/web.go deleted file mode 100644 index dfd766b..0000000 --- a/server/internal/web/web.go +++ /dev/null @@ -1,64 +0,0 @@ -package web - -import ( - "context" - "log" - "net/http" - "time" - - "github.com/kixelated/invoker" -) - -type Server struct { - inner http.Server - config Config -} - -type Config struct { - Addr string - CertFile string - KeyFile string - Fingerprint string // the TLS certificate fingerprint -} - -func New(config Config) (s *Server) { - s = new(Server) - s.config = config - - s.inner = http.Server{ - Addr: config.Addr, - } - - http.HandleFunc("/fingerprint", s.handleFingerprint) - - return s -} - -func (s *Server) Run(ctx context.Context) (err error) { - return invoker.Run(ctx, s.runServe, s.runShutdown) -} - -func (s *Server) runServe(context.Context) (err error) { - // NOTE: Doesn't support context, which is why we need runShutdown - err = s.inner.ListenAndServeTLS(s.config.CertFile, s.config.KeyFile) - log.Println(err) - return err -} - -// Gracefully shut down the server when the context is cancelled -func (s *Server) runShutdown(ctx context.Context) (err error) { - <-ctx.Done() - - timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - _ = s.inner.Shutdown(timeout) - - return ctx.Err() -} - -// Return the sha256 of the certificate as a temporary work-around for local development. -// TODO remove this when WebTransport uses the system CA -func (s *Server) handleFingerprint(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - _, _ = w.Write([]byte(s.config.Fingerprint)) -} diff --git a/server/main.go b/server/main.go deleted file mode 100644 index a0b5f9b..0000000 --- a/server/main.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "context" - "crypto/sha256" - "crypto/tls" - "encoding/hex" - "flag" - "fmt" - "log" - - "github.com/kixelated/invoker" - "github.com/kixelated/warp/server/internal/warp" - "github.com/kixelated/warp/server/internal/web" -) - -func main() { - err := run(context.Background()) - if err != nil { - log.Fatal(err) - } -} - -func run(ctx context.Context) (err error) { - addr := flag.String("addr", ":4443", "HTTPS server address") - cert := flag.String("tls-cert", "../cert/localhost.crt", "TLS certificate file path") - key := flag.String("tls-key", "../cert/localhost.key", "TLS certificate file path") - logDir := flag.String("log-dir", "", "logs will be written to the provided directory") - - dash := flag.String("dash", "../media/playlist.mpd", "DASH playlist path") - - flag.Parse() - - media, err := warp.NewMedia(*dash) - if err != nil { - return fmt.Errorf("failed to open media: %w", err) - } - - tlsCert, err := tls.LoadX509KeyPair(*cert, *key) - if err != nil { - return fmt.Errorf("failed to load TLS certificate: %w", err) - } - - warpConfig := warp.Config{ - Addr: *addr, - Cert: &tlsCert, - LogDir: *logDir, - Media: media, - } - - warpServer, err := warp.New(warpConfig) - if err != nil { - return fmt.Errorf("failed to create warp server: %w", err) - } - - hash := sha256.Sum256(tlsCert.Certificate[0]) - fingerprint := hex.EncodeToString(hash[:]) - - webConfig := web.Config{ - Addr: *addr, - CertFile: *cert, - KeyFile: *key, - Fingerprint: fingerprint, - } - - webServer := web.New(webConfig) - - log.Printf("listening on %s", *addr) - - return invoker.Run(ctx, invoker.Interrupt, warpServer.Run, webServer.Run) -} diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}