From 953f6a3fd702b0f512e2762eb316b8cf49ffbfdd Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Sat, 10 Dec 2022 13:07:33 +0530 Subject: [PATCH 01/12] net: Add QUIC source and sink To test, run receiver as ```bash gst-launch-1.0 -v -e quicsrc caps=audio/x-opus use-datagram=true ! opusparse ! opusdec ! audio/x-raw,format=S16LE,rate=48000,channels=2,layout=interleaved ! audioconvert ! autoaudiosink ``` run sender as ```bash gst-launch-1.0 -v -e audiotestsrc num-buffers=512 ! audio/x-raw,format=S16LE,rate=48000,channels=2,layout=interleaved ! opusenc ! quicsink use-datagram=true ``` Part-of: --- Cargo.lock | 510 +++++++++++++++++++++--------- Cargo.toml | 1 + net/quic/Cargo.toml | 55 ++++ net/quic/build.rs | 3 + net/quic/src/lib.rs | 38 +++ net/quic/src/quicsink/imp.rs | 472 ++++++++++++++++++++++++++++ net/quic/src/quicsink/mod.rs | 26 ++ net/quic/src/quicsrc/imp.rs | 583 +++++++++++++++++++++++++++++++++++ net/quic/src/quicsrc/mod.rs | 36 +++ net/quic/src/utils.rs | 252 +++++++++++++++ net/quic/tests/quic.rs | 111 +++++++ 11 files changed, 1939 insertions(+), 148 deletions(-) create mode 100644 net/quic/Cargo.toml create mode 100644 net/quic/build.rs create mode 100644 net/quic/src/lib.rs create mode 100644 net/quic/src/quicsink/imp.rs create mode 100644 net/quic/src/quicsink/mod.rs create mode 100644 net/quic/src/quicsrc/imp.rs create mode 100644 net/quic/src/quicsrc/mod.rs create mode 100644 net/quic/src/utils.rs create mode 100644 net/quic/tests/quic.rs diff --git a/Cargo.lock b/Cargo.lock index c77ae4e6..b4e6dc6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,7 +352,7 @@ dependencies = [ "hex", "http 0.2.12", "hyper 0.14.28", - "ring", + "ring 0.17.8", "time", "tokio", "tracing", @@ -611,7 +611,7 @@ dependencies = [ "once_cell", "p256", "percent-encoding", - "ring", + "ring 0.17.8", "sha2", "subtle", "time", @@ -1744,7 +1744,7 @@ dependencies = [ "futures-core", "futures-sink", "nanorand", - "spin", + "spin 0.9.8", ] [[package]] @@ -2208,11 +2208,11 @@ dependencies = [ "byte-slice-cast", "ebur128", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", "gstreamer-audio", - "gstreamer-base", - "gstreamer-check", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "hrtf", "nnnoiseless", "num-traits", @@ -2239,10 +2239,10 @@ dependencies = [ "futures", "gio", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-base", - "gstreamer-check", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "once_cell", "percent-encoding", "rand", @@ -2261,9 +2261,9 @@ dependencies = [ "cdg", "cdg_renderer", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", - "gstreamer-base", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "image", "once_cell", @@ -2277,9 +2277,9 @@ dependencies = [ "byte-slice-cast", "claxon", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-check", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "once_cell", ] @@ -2296,9 +2296,9 @@ dependencies = [ "chrono", "either", "gst-plugin-version-helper", - "gstreamer", - "gstreamer-base", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "once_cell", "pango", @@ -2319,10 +2319,10 @@ dependencies = [ "byte-slice-cast", "csound", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-base", - "gstreamer-check", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "once_cell", ] @@ -2332,8 +2332,8 @@ version = "0.13.0-alpha.1" dependencies = [ "dav1d", "gst-plugin-version-helper", - "gstreamer", - "gstreamer-base", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "num_cpus", "once_cell", @@ -2346,11 +2346,11 @@ dependencies = [ "gio", "gst-plugin-gtk4", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", "gstreamer-audio", - "gstreamer-base", - "gstreamer-check", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "gtk4", "once_cell", @@ -2364,8 +2364,8 @@ dependencies = [ "byte-slice-cast", "ffv1", "gst-plugin-version-helper", - "gstreamer", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "once_cell", ] @@ -2375,8 +2375,8 @@ name = "gst-plugin-file" version = "0.13.0-alpha.1" dependencies = [ "gst-plugin-version-helper", - "gstreamer", - "gstreamer-base", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "once_cell", "url", ] @@ -2388,9 +2388,9 @@ dependencies = [ "byteorder", "flavors", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-base", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "nom", "num-rational", "once_cell", @@ -2405,11 +2405,11 @@ dependencies = [ "chrono", "dash-mpd", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", "gstreamer-audio", - "gstreamer-base", - "gstreamer-check", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-pbutils", "gstreamer-video", "m3u8-rs", @@ -2425,8 +2425,8 @@ dependencies = [ "atomic_refcell", "gif", "gst-plugin-version-helper", - "gstreamer", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "once_cell", ] @@ -2437,9 +2437,9 @@ version = "0.13.0-alpha.1" dependencies = [ "anyhow", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", - "gstreamer-check", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "once_cell", ] @@ -2453,9 +2453,9 @@ dependencies = [ "gdk4-win32", "gdk4-x11", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-allocators", - "gstreamer-base", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-gl", "gstreamer-gl-egl", "gstreamer-gl-wayland", @@ -2474,10 +2474,10 @@ dependencies = [ "chrono", "gio", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", "gstreamer-audio", - "gstreamer-check", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-pbutils", "gstreamer-video", "m3u8-rs", @@ -2491,10 +2491,10 @@ version = "0.13.0-alpha.1" dependencies = [ "byte-slice-cast", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-base", - "gstreamer-check", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "num-traits", "once_cell", @@ -2507,9 +2507,9 @@ dependencies = [ "anyhow", "futures", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", - "gstreamer-check", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-utils", "once_cell", "pretty_assertions", @@ -2523,8 +2523,8 @@ name = "gst-plugin-json" version = "0.13.0-alpha.1" dependencies = [ "gst-plugin-version-helper", - "gstreamer", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "once_cell", "serde", "serde_json", @@ -2537,9 +2537,9 @@ dependencies = [ "atomic_refcell", "byte-slice-cast", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-check", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "lewton", "once_cell", ] @@ -2551,9 +2551,9 @@ dependencies = [ "gio", "gst-plugin-gtk4", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-check", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gtk4", "num-rational", "once_cell", @@ -2566,9 +2566,9 @@ version = "0.13.0-alpha.1" dependencies = [ "anyhow", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-base", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-pbutils", "gstreamer-video", "once_cell", @@ -2587,9 +2587,9 @@ dependencies = [ "data-encoding", "glib", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-base", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "libloading", "once_cell", @@ -2605,8 +2605,8 @@ dependencies = [ "cairo-rs", "chrono", "gst-plugin-version-helper", - "gstreamer", - "gstreamer-base", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-rtp", "gstreamer-video", "once_cell", @@ -2623,7 +2623,7 @@ dependencies = [ "atomic_refcell", "glib", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "once_cell", ] @@ -2633,22 +2633,42 @@ name = "gst-plugin-png" version = "0.13.0-alpha.1" dependencies = [ "gst-plugin-version-helper", - "gstreamer", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "once_cell", "parking_lot", "png", ] +[[package]] +name = "gst-plugin-quic" +version = "0.13.0-alpha.1" +dependencies = [ + "bytes", + "futures", + "gst-plugin-version-helper", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs)", + "once_cell", + "quinn", + "rcgen", + "rustls", + "rustls-pemfile 1.0.4", + "serial_test", + "thiserror", + "tokio", +] + [[package]] name = "gst-plugin-raptorq" version = "0.13.0-alpha.1" dependencies = [ "gst-plugin-version-helper", - "gstreamer", - "gstreamer-base", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-rtp", "once_cell", "rand", @@ -2661,8 +2681,8 @@ version = "0.13.0-alpha.1" dependencies = [ "atomic_refcell", "gst-plugin-version-helper", - "gstreamer", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "once_cell", "rav1e", @@ -2673,8 +2693,8 @@ name = "gst-plugin-regex" version = "0.13.0-alpha.1" dependencies = [ "gst-plugin-version-helper", - "gstreamer", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "once_cell", "regex", ] @@ -2686,8 +2706,8 @@ dependencies = [ "bytes", "futures", "gst-plugin-version-helper", - "gstreamer", - "gstreamer-base", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "headers 0.4.0", "http-body-util", "hyper 1.3.1", @@ -2709,10 +2729,10 @@ dependencies = [ "byte-slice-cast", "chrono", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", "gstreamer-audio", - "gstreamer-check", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-rtp", "gstreamer-video", "hex", @@ -2734,7 +2754,7 @@ dependencies = [ "data-encoding", "futures", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", "gstreamer-net", "gstreamer-pbutils", @@ -2755,10 +2775,10 @@ version = "0.13.0-alpha.1" dependencies = [ "clap", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", - "gstreamer-base", - "gstreamer-check", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "hex", "once_cell", "pretty_assertions", @@ -2776,8 +2796,8 @@ dependencies = [ "anyhow", "futures", "gst-plugin-version-helper", - "gstreamer", - "gstreamer-base", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "librespot", "once_cell", "tokio", @@ -2789,7 +2809,7 @@ name = "gst-plugin-textahead" version = "0.13.0-alpha.1" dependencies = [ "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "once_cell", ] @@ -2798,8 +2818,8 @@ name = "gst-plugin-textwrap" version = "0.13.0-alpha.1" dependencies = [ "gst-plugin-version-helper", - "gstreamer", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "hyphenation", "once_cell", "textwrap", @@ -2818,10 +2838,10 @@ dependencies = [ "futures", "gio", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", "gstreamer-audio", - "gstreamer-check", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-net", "gstreamer-rtp", "once_cell", @@ -2844,9 +2864,9 @@ dependencies = [ "gio", "gst-plugin-gtk4", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-check", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "gtk4", "once_cell", @@ -2859,7 +2879,7 @@ version = "0.13.0-alpha.1" dependencies = [ "anyhow", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "once_cell", "regex", "signal-hook", @@ -2871,9 +2891,9 @@ version = "0.13.0-alpha.1" dependencies = [ "byte-slice-cast", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", - "gstreamer-base", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "num-traits", "once_cell", @@ -2886,7 +2906,7 @@ dependencies = [ "anyhow", "clap", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", "more-asserts", "once_cell", @@ -2912,9 +2932,9 @@ dependencies = [ "color-thief", "dssim-core", "gst-plugin-version-helper", - "gstreamer", - "gstreamer-base", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "image", "image_hasher", @@ -2927,8 +2947,8 @@ name = "gst-plugin-webp" version = "0.13.0-alpha.1" dependencies = [ "gst-plugin-version-helper", - "gstreamer", - "gstreamer-check", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video", "libwebp-sys2", "once_cell", @@ -2959,10 +2979,10 @@ dependencies = [ "gst-plugin-rtp", "gst-plugin-version-helper", "gst-plugin-webrtc-signalling-protocol", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", "gstreamer-audio", - "gstreamer-base", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-net", "gstreamer-rtp", "gstreamer-sdp", @@ -3031,7 +3051,7 @@ dependencies = [ "bytes", "futures", "gst-plugin-version-helper", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-sdp", "gstreamer-webrtc", "once_cell", @@ -3050,7 +3070,7 @@ dependencies = [ "futures-core", "futures-util", "glib", - "gstreamer-sys", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "itertools 0.12.1", "libc", "muldiv", @@ -3066,13 +3086,37 @@ dependencies = [ "thiserror", ] +[[package]] +name = "gstreamer" +version = "0.23.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#b7b5352353e147fccc00ff684dffb65c21a6e6b1" +dependencies = [ + "cfg-if", + "futures-channel", + "futures-core", + "futures-util", + "glib", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs)", + "itertools 0.12.1", + "libc", + "muldiv", + "num-integer", + "num-rational", + "once_cell", + "option-operations", + "paste", + "pin-project-lite", + "smallvec", + "thiserror", +] + [[package]] name = "gstreamer-allocators" version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-allocators-sys", "libc", "once_cell", @@ -3085,7 +3129,7 @@ source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main# dependencies = [ "glib-sys", "gobject-sys", - "gstreamer-sys", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "libc", "system-deps", ] @@ -3098,9 +3142,9 @@ dependencies = [ "futures-core", "futures-sink", "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app-sys", - "gstreamer-base", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "libc", ] @@ -3110,8 +3154,8 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib-sys", - "gstreamer-base-sys", - "gstreamer-sys", + "gstreamer-base-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "libc", "system-deps", ] @@ -3123,9 +3167,9 @@ source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main# dependencies = [ "cfg-if", "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio-sys", - "gstreamer-base", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "libc", "once_cell", "serde", @@ -3139,8 +3183,8 @@ source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main# dependencies = [ "glib-sys", "gobject-sys", - "gstreamer-base-sys", - "gstreamer-sys", + "gstreamer-base-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "libc", "system-deps", ] @@ -3153,8 +3197,21 @@ dependencies = [ "atomic_refcell", "cfg-if", "glib", - "gstreamer", - "gstreamer-base-sys", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "libc", +] + +[[package]] +name = "gstreamer-base" +version = "0.23.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#b7b5352353e147fccc00ff684dffb65c21a6e6b1" +dependencies = [ + "atomic_refcell", + "cfg-if", + "glib", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs)", + "gstreamer-base-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs)", "libc", ] @@ -3165,7 +3222,19 @@ source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main# dependencies = [ "glib-sys", "gobject-sys", - "gstreamer-sys", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-base-sys" +version = "0.23.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#b7b5352353e147fccc00ff684dffb65c21a6e6b1" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs)", "libc", "system-deps", ] @@ -3176,8 +3245,18 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib", - "gstreamer", - "gstreamer-check-sys", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-check-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", +] + +[[package]] +name = "gstreamer-check" +version = "0.23.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#b7b5352353e147fccc00ff684dffb65c21a6e6b1" +dependencies = [ + "glib", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs)", + "gstreamer-check-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs)", ] [[package]] @@ -3187,7 +3266,19 @@ source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main# dependencies = [ "glib-sys", "gobject-sys", - "gstreamer-sys", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-check-sys" +version = "0.23.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#b7b5352353e147fccc00ff684dffb65c21a6e6b1" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs)", "libc", "system-deps", ] @@ -3198,8 +3289,8 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib", - "gstreamer", - "gstreamer-base", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-gl-sys", "gstreamer-video", "libc", @@ -3212,7 +3303,7 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-gl", "gstreamer-gl-egl-sys", "libc", @@ -3236,8 +3327,8 @@ source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main# dependencies = [ "glib-sys", "gobject-sys", - "gstreamer-base-sys", - "gstreamer-sys", + "gstreamer-base-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video-sys", "libc", "system-deps", @@ -3249,7 +3340,7 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-gl", "gstreamer-gl-wayland-sys", "libc", @@ -3272,7 +3363,7 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-gl", "gstreamer-gl-x11-sys", "libc", @@ -3296,7 +3387,7 @@ source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main# dependencies = [ "gio", "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-net-sys", ] @@ -3307,7 +3398,7 @@ source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main# dependencies = [ "gio-sys", "glib-sys", - "gstreamer-sys", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "libc", "system-deps", ] @@ -3318,7 +3409,7 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-audio", "gstreamer-pbutils-sys", "gstreamer-video", @@ -3334,7 +3425,7 @@ dependencies = [ "glib-sys", "gobject-sys", "gstreamer-audio-sys", - "gstreamer-sys", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video-sys", "libc", "system-deps", @@ -3346,7 +3437,7 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-rtp-sys", "libc", ] @@ -3357,8 +3448,8 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib-sys", - "gstreamer-base-sys", - "gstreamer-sys", + "gstreamer-base-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "libc", "system-deps", ] @@ -3369,7 +3460,7 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-sdp-sys", ] @@ -3379,7 +3470,7 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib-sys", - "gstreamer-sys", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "libc", "system-deps", ] @@ -3395,12 +3486,23 @@ dependencies = [ "system-deps", ] +[[package]] +name = "gstreamer-sys" +version = "0.23.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#b7b5352353e147fccc00ff684dffb65c21a6e6b1" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "gstreamer-utils" version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-app", "gstreamer-video", "once_cell", @@ -3415,8 +3517,8 @@ dependencies = [ "cfg-if", "futures-channel", "glib", - "gstreamer", - "gstreamer-base", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-base 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-video-sys", "libc", "once_cell", @@ -3431,8 +3533,8 @@ source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main# dependencies = [ "glib-sys", "gobject-sys", - "gstreamer-base-sys", - "gstreamer-sys", + "gstreamer-base-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "libc", "system-deps", ] @@ -3443,7 +3545,7 @@ version = "0.23.0" source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#e117010bc001f87551713c528bf3abff5c9848ae" dependencies = [ "glib", - "gstreamer", + "gstreamer 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "gstreamer-sdp", "gstreamer-webrtc-sys", "libc", @@ -3456,7 +3558,7 @@ source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main# dependencies = [ "glib-sys", "gstreamer-sdp-sys", - "gstreamer-sys", + "gstreamer-sys 0.23.0 (git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main)", "libc", "system-deps", ] @@ -3783,7 +3885,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -4113,7 +4215,7 @@ checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" dependencies = [ "base64 0.21.7", "js-sys", - "ring", + "ring 0.17.8", "serde", "serde_json", ] @@ -4606,7 +4708,7 @@ dependencies = [ "log", "memchr", "mime", - "spin", + "spin 0.9.8", "version_check", ] @@ -5045,6 +5147,16 @@ dependencies = [ "hmac 0.11.0", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.0", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -5272,7 +5384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" dependencies = [ "bytes", - "heck 0.4.1", + "heck 0.5.0", "itertools 0.12.1", "log", "multimap 0.10.0", @@ -5359,6 +5471,54 @@ dependencies = [ "serde", ] +[[package]] +name = "quinn" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" +dependencies = [ + "bytes", + "rand", + "ring 0.16.20", + "rustc-hash", + "rustls", + "rustls-native-certs", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" +dependencies = [ + "bytes", + "libc", + "socket2 0.5.6", + "tracing", + "windows-sys 0.48.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -5470,6 +5630,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48406db8ac1f3cbc7dcdb56ec355343817958a356ff430259bb07baf7607e1e1" +dependencies = [ + "pem", + "ring 0.17.8", + "time", + "yasna", +] + [[package]] name = "realfft" version = "3.3.0" @@ -5644,6 +5816,21 @@ dependencies = [ "bytemuck", ] +[[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 0.7.1", + "web-sys", + "winapi", +] + [[package]] name = "ring" version = "0.17.8" @@ -5654,8 +5841,8 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin", - "untrusted", + "spin 0.9.8", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -5711,6 +5898,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -5764,7 +5957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "log", - "ring", + "ring 0.17.8", "rustls-webpki", "sct", ] @@ -5812,8 +6005,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring", - "untrusted", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] @@ -5867,8 +6060,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] @@ -6212,6 +6405,12 @@ dependencies = [ "serde", ] +[[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" @@ -6821,6 +7020,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -7329,6 +7534,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "zerocopy" version = "0.6.6" diff --git a/Cargo.toml b/Cargo.toml index 9d803a18..8b68ca78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "net/webrtc", "net/webrtc/protocol", "net/webrtc/signalling", + "net/quic", "text/ahead", "text/json", diff --git a/net/quic/Cargo.toml b/net/quic/Cargo.toml new file mode 100644 index 00000000..f2b486c3 --- /dev/null +++ b/net/quic/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "gst-plugin-quic" +version.workspace = true +authors = ["Sanchayan Maity +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 +#![allow(clippy::non_send_fields_in_send_ty, unused_doc_comments)] + +/** + * plugin-quic: + * + * Since: plugins-rs-0.11.0 + */ +use gst::glib; +mod quicsink; +mod quicsrc; +mod utils; + +fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + quicsink::register(plugin)?; + quicsrc::register(plugin)?; + + Ok(()) +} + +gst::plugin_define!( + quic, + env!("CARGO_PKG_DESCRIPTION"), + plugin_init, + concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")), + "MPL", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_REPOSITORY"), + env!("BUILD_REL_DATE") +); diff --git a/net/quic/src/quicsink/imp.rs b/net/quic/src/quicsink/imp.rs new file mode 100644 index 00000000..ee53b804 --- /dev/null +++ b/net/quic/src/quicsink/imp.rs @@ -0,0 +1,472 @@ +// Copyright (C) 2024, Asymptotic Inc. +// Author: Sanchayan Maity +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use crate::utils::{ + client_endpoint, make_socket_addr, wait, WaitError, CONNECTION_CLOSE_CODE, CONNECTION_CLOSE_MSG, +}; +use bytes::Bytes; +use futures::future; +use gst::{glib, prelude::*, subclass::prelude::*}; +use gst_base::subclass::prelude::*; +use once_cell::sync::Lazy; +use quinn::{Connection, SendStream}; +use std::net::SocketAddr; +use std::sync::Mutex; + +static DEFAULT_SERVER_NAME: &str = "localhost"; +static DEFAULT_SERVER_ADDR: &str = "127.0.0.1:5000"; +static DEFAULT_CLIENT_ADDR: &str = "127.0.0.1:5001"; +const DEFAULT_TIMEOUT: u32 = 15; +const DEFAULT_SECURE_CONNECTION: bool = true; + +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new("quicsink", gst::DebugColorFlags::empty(), Some("QUIC Sink")) +}); + +struct Started { + connection: Connection, + stream: Option, +} + +#[derive(Default)] +enum State { + #[default] + Stopped, + Started(Started), +} + +#[derive(Clone, Debug)] +struct Settings { + client_address: SocketAddr, + server_address: SocketAddr, + server_name: String, + timeout: u32, + secure_conn: bool, + use_datagram: bool, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + client_address: DEFAULT_CLIENT_ADDR.parse::().unwrap(), + server_address: DEFAULT_SERVER_ADDR.parse::().unwrap(), + server_name: DEFAULT_SERVER_NAME.to_string(), + timeout: DEFAULT_TIMEOUT, + secure_conn: DEFAULT_SECURE_CONNECTION, + use_datagram: false, + } + } +} + +pub struct QuicSink { + settings: Mutex, + state: Mutex, + canceller: Mutex>, +} + +impl Default for QuicSink { + fn default() -> Self { + Self { + settings: Mutex::new(Settings::default()), + state: Mutex::new(State::default()), + canceller: Mutex::new(None), + } + } +} + +impl GstObjectImpl for QuicSink {} + +impl ElementImpl for QuicSink { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + gst::subclass::ElementMetadata::new( + "QUIC Sink", + "Source/Network/QUIC", + "Send data over the network via QUIC", + "Sanchayan Maity ", + ) + }); + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + let sink_pad_template = gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &gst::Caps::new_any(), + ) + .unwrap(); + + vec![sink_pad_template] + }); + + PAD_TEMPLATES.as_ref() + } +} + +impl ObjectImpl for QuicSink { + fn constructed(&self) { + self.parent_constructed(); + } + + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpecString::builder("server-name") + .nick("QUIC server name") + .blurb("Name of the QUIC server which is in server certificate") + .build(), + glib::ParamSpecString::builder("server-address") + .nick("QUIC server address") + .blurb("Address of the QUIC server to connect to e.g. 127.0.0.1:5000") + .build(), + glib::ParamSpecString::builder("client-address") + .nick("QUIC client address") + .blurb("Address to be used by this QUIC client e.g. 127.0.0.1:5001") + .build(), + glib::ParamSpecUInt::builder("timeout") + .nick("Timeout") + .blurb("Value in seconds to timeout QUIC endpoint requests (0 = No timeout).") + .maximum(3600) + .default_value(DEFAULT_TIMEOUT) + .readwrite() + .build(), + glib::ParamSpecBoolean::builder("secure-connection") + .nick("Use secure connection") + .blurb("Use certificates for QUIC connection. False: Insecure connection, True: Secure connection.") + .default_value(DEFAULT_SECURE_CONNECTION) + .build(), + glib::ParamSpecBoolean::builder("use-datagram") + .nick("Use datagram") + .blurb("Use datagram for lower latency, unreliable messaging") + .default_value(false) + .build(), + ] + }); + + PROPERTIES.as_ref() + } + + fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + match pspec.name() { + "server-name" => { + let mut settings = self.settings.lock().unwrap(); + settings.server_name = value.get::().expect("type checked upstream"); + } + "server-address" => { + let addr = value.get::().expect("type checked upstream"); + let addr = make_socket_addr(&addr); + match addr { + Ok(server_address) => { + let mut settings = self.settings.lock().unwrap(); + settings.server_address = server_address; + } + Err(e) => gst::element_imp_error!( + self, + gst::ResourceError::Failed, + ["Invalid server address: {}", e] + ), + } + } + "client-address" => { + let addr = value.get::().expect("type checked upstream"); + let addr = make_socket_addr(&addr); + match addr { + Ok(client_address) => { + let mut settings = self.settings.lock().unwrap(); + settings.client_address = client_address; + } + Err(e) => gst::element_imp_error!( + self, + gst::ResourceError::Failed, + ["Invalid client address: {}", e] + ), + } + } + "timeout" => { + let mut settings = self.settings.lock().unwrap(); + settings.timeout = value.get().expect("type checked upstream"); + } + "secure-connection" => { + let mut settings = self.settings.lock().unwrap(); + settings.secure_conn = value.get().expect("type checked upstream"); + } + "use-datagram" => { + let mut settings = self.settings.lock().unwrap(); + settings.use_datagram = value.get().expect("type checked upstream"); + } + _ => unimplemented!(), + } + } + + fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "server-name" => { + let settings = self.settings.lock().unwrap(); + settings.server_name.to_value() + } + "server-address" => { + let settings = self.settings.lock().unwrap(); + settings.server_address.to_string().to_value() + } + "client-address" => { + let settings = self.settings.lock().unwrap(); + settings.client_address.to_string().to_value() + } + "timeout" => { + let settings = self.settings.lock().unwrap(); + settings.timeout.to_value() + } + "secure-connection" => { + let settings = self.settings.lock().unwrap(); + settings.secure_conn.to_value() + } + "use-datagram" => { + let settings = self.settings.lock().unwrap(); + settings.use_datagram.to_value() + } + _ => unimplemented!(), + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for QuicSink { + const NAME: &'static str = "GstQUICSink"; + type Type = super::QuicSink; + type ParentType = gst_base::BaseSink; +} + +impl BaseSinkImpl for QuicSink { + fn start(&self) -> Result<(), gst::ErrorMessage> { + let settings = self.settings.lock().unwrap(); + let timeout = settings.timeout; + drop(settings); + + let mut state = self.state.lock().unwrap(); + + if let State::Started { .. } = *state { + unreachable!("QuicSink is already started"); + } + + match wait(&self.canceller, self.establish_connection(), timeout) { + Ok(Ok((c, s))) => { + *state = State::Started(Started { + connection: c, + stream: s, + }); + + gst::info!(CAT, imp: self, "Started"); + + Ok(()) + } + Ok(Err(e)) => match e { + WaitError::FutureAborted => { + gst::warning!(CAT, imp: self, "Connection aborted"); + Ok(()) + } + WaitError::FutureError(err) => { + gst::error!(CAT, imp: self, "Connection request failed: {}", err); + Err(gst::error_msg!( + gst::ResourceError::Failed, + ["Connection request failed: {}", err] + )) + } + }, + Err(e) => { + gst::error!(CAT, imp: self, "Failed to establish a connection: {:?}", e); + Err(gst::error_msg!( + gst::ResourceError::Failed, + ["Failed to establish a connection: {:?}", e] + )) + } + } + } + + fn stop(&self) -> Result<(), gst::ErrorMessage> { + let settings = self.settings.lock().unwrap(); + let timeout = settings.timeout; + let use_datagram = settings.use_datagram; + drop(settings); + + let mut state = self.state.lock().unwrap(); + + if let State::Started(ref mut state) = *state { + let connection = &state.connection; + let mut close_msg = CONNECTION_CLOSE_MSG.to_string(); + + if !use_datagram { + let send = &mut state.stream.as_mut().unwrap(); + + // Shutdown stream gracefully + match wait(&self.canceller, send.finish(), timeout) { + Ok(r) => { + if let Err(e) = r { + close_msg = format!("Stream finish request error: {}", e); + gst::error!(CAT, imp: self, "{}", close_msg); + } + } + Err(e) => match e { + WaitError::FutureAborted => { + close_msg = "Stream finish request aborted".to_string(); + gst::warning!(CAT, imp: self, "{}", close_msg); + } + WaitError::FutureError(e) => { + close_msg = format!("Stream finish request future error: {}", e); + gst::error!(CAT, imp: self, "{}", close_msg); + } + }, + }; + } + + connection.close(CONNECTION_CLOSE_CODE.into(), close_msg.as_bytes()); + } + + *state = State::Stopped; + + gst::info!(CAT, imp: self, "Stopped"); + + Ok(()) + } + + fn render(&self, buffer: &gst::Buffer) -> Result { + if let State::Stopped = *self.state.lock().unwrap() { + gst::element_imp_error!(self, gst::CoreError::Failed, ["Not started yet"]); + return Err(gst::FlowError::Error); + } + + gst::trace!(CAT, imp: self, "Rendering {:?}", buffer); + + let map = buffer.map_readable().map_err(|_| { + gst::element_imp_error!(self, gst::CoreError::Failed, ["Failed to map buffer"]); + gst::FlowError::Error + })?; + + match self.send_buffer(&map) { + Ok(_) => Ok(gst::FlowSuccess::Ok), + Err(err) => match err { + Some(error_message) => { + gst::error!(CAT, imp: self, "Data sending failed: {}", error_message); + self.post_error_message(error_message); + Err(gst::FlowError::Error) + } + _ => { + gst::info!(CAT, imp: self, "Send interrupted. Flushing..."); + Err(gst::FlowError::Flushing) + } + }, + } + } +} + +impl QuicSink { + fn send_buffer(&self, src: &[u8]) -> Result<(), Option> { + let settings = self.settings.lock().unwrap(); + let timeout = settings.timeout; + let use_datagram = settings.use_datagram; + drop(settings); + + let mut state = self.state.lock().unwrap(); + + let (conn, stream) = match *state { + State::Started(Started { + ref connection, + ref mut stream, + }) => (connection, stream), + State::Stopped => { + return Err(Some(gst::error_msg!( + gst::LibraryError::Failed, + ["Cannot send before start()"] + ))); + } + }; + + if use_datagram { + match conn.send_datagram(Bytes::copy_from_slice(src)) { + Ok(_) => Ok(()), + Err(e) => Err(Some(gst::error_msg!( + gst::ResourceError::Failed, + ["Sending data failed: {}", e] + ))), + } + } else { + let send = &mut stream.as_mut().unwrap(); + + match wait(&self.canceller, send.write_all(src), timeout) { + Ok(Ok(_)) => Ok(()), + Ok(Err(e)) => Err(Some(gst::error_msg!( + gst::ResourceError::Failed, + ["Sending data failed: {}", e] + ))), + Err(e) => match e { + WaitError::FutureAborted => { + gst::warning!(CAT, imp: self, "Sending aborted"); + Ok(()) + } + WaitError::FutureError(e) => Err(Some(gst::error_msg!( + gst::ResourceError::Failed, + ["Sending data failed: {}", e] + ))), + }, + } + } + } + + async fn establish_connection(&self) -> Result<(Connection, Option), WaitError> { + let client_addr; + let server_addr; + let server_name; + let use_datagram; + let secure_conn; + + { + let settings = self.settings.lock().unwrap(); + client_addr = settings.client_address; + server_addr = settings.server_address; + server_name = settings.server_name.clone(); + use_datagram = settings.use_datagram; + secure_conn = settings.secure_conn; + } + + let endpoint = client_endpoint(client_addr, secure_conn).map_err(|err| { + WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Failed to configure endpoint: {}", err] + )) + })?; + + let connection = endpoint + .connect(server_addr, &server_name) + .unwrap() + .await + .map_err(|err| { + WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Connection error: {}", err] + )) + })?; + + let stream = if !use_datagram { + let res = connection.open_uni().await.map_err(|err| { + WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Failed to open stream: {}", err] + )) + })?; + + Some(res) + } else { + None + }; + + Ok((connection, stream)) + } +} diff --git a/net/quic/src/quicsink/mod.rs b/net/quic/src/quicsink/mod.rs new file mode 100644 index 00000000..728932ae --- /dev/null +++ b/net/quic/src/quicsink/mod.rs @@ -0,0 +1,26 @@ +// Copyright (C) 2024, Asymptotic Inc. +// Author: Sanchayan Maity +//G +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use gst::glib; +use gst::prelude::*; + +pub mod imp; + +glib::wrapper! { + pub struct QuicSink(ObjectSubclass) @extends gst_base::BaseSink, gst::Element, gst::Object; +} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "quicsink", + gst::Rank::MARGINAL, + QuicSink::static_type(), + ) +} diff --git a/net/quic/src/quicsrc/imp.rs b/net/quic/src/quicsrc/imp.rs new file mode 100644 index 00000000..3dc666a3 --- /dev/null +++ b/net/quic/src/quicsrc/imp.rs @@ -0,0 +1,583 @@ +// Copyright (C) 2024, Asymptotic Inc. +// Author: Sanchayan Maity +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use super::QuicPrivateKeyType; +use crate::utils::{ + make_socket_addr, server_endpoint, wait, WaitError, CONNECTION_CLOSE_CODE, CONNECTION_CLOSE_MSG, +}; +use bytes::Bytes; +use futures::future; +use gst::{glib, prelude::*, subclass::prelude::*}; +use gst_base::prelude::*; +use gst_base::subclass::base_src::CreateSuccess; +use gst_base::subclass::prelude::*; +use once_cell::sync::Lazy; +use quinn::{Connection, ConnectionError, RecvStream}; +use std::net::SocketAddr; +use std::path::PathBuf; +use std::sync::Mutex; + +static DEFAULT_SERVER_NAME: &str = "localhost"; +static DEFAULT_SERVER_ADDR: &str = "127.0.0.1:5000"; +const DEFAULT_TIMEOUT: u32 = 15; +const DEFAULT_PRIVATE_KEY_TYPE: QuicPrivateKeyType = QuicPrivateKeyType::Pkcs8; +const DEFAULT_SECURE_CONNECTION: bool = true; + +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new( + "quicsrc", + gst::DebugColorFlags::empty(), + Some("QUIC Source"), + ) +}); + +struct Started { + connection: Connection, + stream: Option, +} + +#[derive(Default)] +enum State { + #[default] + Stopped, + Started(Started), +} + +#[derive(Clone, Debug)] +struct Settings { + server_address: SocketAddr, + server_name: String, + timeout: u32, + secure_conn: bool, + caps: gst::Caps, + use_datagram: bool, + certificate_path: Option, + private_key_type: QuicPrivateKeyType, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + server_address: DEFAULT_SERVER_ADDR.parse::().unwrap(), + server_name: DEFAULT_SERVER_NAME.to_string(), + timeout: DEFAULT_TIMEOUT, + secure_conn: DEFAULT_SECURE_CONNECTION, + caps: gst::Caps::new_any(), + use_datagram: false, + certificate_path: None, + private_key_type: DEFAULT_PRIVATE_KEY_TYPE, + } + } +} + +pub struct QuicSrc { + settings: Mutex, + state: Mutex, + canceller: Mutex>, +} + +impl Default for QuicSrc { + fn default() -> Self { + Self { + settings: Mutex::new(Settings::default()), + state: Mutex::new(State::default()), + canceller: Mutex::new(None), + } + } +} + +impl GstObjectImpl for QuicSrc {} + +impl ElementImpl for QuicSrc { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + #[cfg(feature = "doc")] + QuicPrivateKeyType::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty()); + gst::subclass::ElementMetadata::new( + "QUIC Source", + "Source/Network/QUIC", + "Receive data over the network via QUIC", + "Sanchayan Maity ", + ) + }); + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + let sink_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &gst::Caps::new_any(), + ) + .unwrap(); + + vec![sink_pad_template] + }); + + PAD_TEMPLATES.as_ref() + } + + fn change_state( + &self, + transition: gst::StateChange, + ) -> Result { + if transition == gst::StateChange::NullToReady { + let settings = self.settings.lock().unwrap(); + + /* + * Fail the state change if a secure connection was requested but + * no certificate path was provided. + */ + if settings.secure_conn && settings.certificate_path.is_none() { + gst::error!( + CAT, + imp: self, + "Certificate path not provided for secure connection" + ); + return Err(gst::StateChangeError); + } + } + + self.parent_change_state(transition) + } +} + +impl ObjectImpl for QuicSrc { + fn constructed(&self) { + self.parent_constructed(); + self.obj().set_format(gst::Format::Bytes); + } + + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpecString::builder("server-name") + .nick("QUIC server name") + .blurb("Name of the QUIC server which is in server certificate") + .build(), + glib::ParamSpecString::builder("server-address") + .nick("QUIC server address") + .blurb("Address of the QUIC server to connect to e.g. 127.0.0.1:5000") + .build(), + glib::ParamSpecUInt::builder("timeout") + .nick("Timeout") + .blurb("Value in seconds to timeout QUIC endpoint requests (0 = No timeout).") + .maximum(3600) + .default_value(DEFAULT_TIMEOUT) + .readwrite() + .build(), + glib::ParamSpecBoolean::builder("secure-connection") + .nick("Use secure connection") + .blurb("Use certificates for QUIC connection. False: Insecure connection, True: Secure connection.") + .default_value(DEFAULT_SECURE_CONNECTION) + .build(), + glib::ParamSpecString::builder("certificate-path") + .nick("Certificate Path") + .blurb("Path where the certificate files cert.pem and privkey.pem are stored") + .build(), + glib::ParamSpecBoxed::builder::("caps") + .nick("caps") + .blurb("The caps of the source pad") + .build(), + glib::ParamSpecBoolean::builder("use-datagram") + .nick("Use datagram") + .blurb("Use datagram for lower latency, unreliable messaging") + .default_value(false) + .build(), + glib::ParamSpecEnum::builder_with_default::("private-key-type", DEFAULT_PRIVATE_KEY_TYPE) + .nick("Whether PKCS8 or RSA key type is considered for private key") + .blurb("Read given private key as PKCS8 or RSA") + .build(), + ] + }); + + PROPERTIES.as_ref() + } + + fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + match pspec.name() { + "server-name" => { + let mut settings = self.settings.lock().unwrap(); + settings.server_name = value.get::().expect("type checked upstream"); + } + "server-address" => { + let addr = value.get::().expect("type checked upstream"); + let addr = make_socket_addr(&addr); + match addr { + Ok(server_address) => { + let mut settings = self.settings.lock().unwrap(); + settings.server_address = server_address; + } + Err(e) => gst::element_imp_error!( + self, + gst::ResourceError::Failed, + ["Invalid server address: {}", e] + ), + } + } + "caps" => { + let mut settings = self.settings.lock().unwrap(); + settings.caps = value + .get::>() + .expect("type checked upstream") + .unwrap_or_else(gst::Caps::new_any); + + let srcpad = self.obj().static_pad("src").expect("source pad expected"); + srcpad.mark_reconfigure(); + } + "timeout" => { + let mut settings = self.settings.lock().unwrap(); + settings.timeout = value.get().expect("type checked upstream"); + } + "secure-connection" => { + let mut settings = self.settings.lock().unwrap(); + settings.secure_conn = value.get().expect("type checked upstream"); + } + "certificate-path" => { + let value: String = value.get().unwrap(); + let mut settings = self.settings.lock().unwrap(); + settings.certificate_path = Some(value.into()); + } + "use-datagram" => { + let mut settings = self.settings.lock().unwrap(); + settings.use_datagram = value.get().expect("type checked upstream"); + } + "private-key-type" => { + let mut settings = self.settings.lock().unwrap(); + settings.private_key_type = value + .get::() + .expect("type checked upstream"); + } + _ => unimplemented!(), + } + } + + fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "server-name" => { + let settings = self.settings.lock().unwrap(); + settings.server_name.to_value() + } + "server-address" => { + let settings = self.settings.lock().unwrap(); + settings.server_address.to_string().to_value() + } + "caps" => { + let settings = self.settings.lock().unwrap(); + settings.caps.to_value() + } + "timeout" => { + let settings = self.settings.lock().unwrap(); + settings.timeout.to_value() + } + "secure-connection" => { + let settings = self.settings.lock().unwrap(); + settings.secure_conn.to_value() + } + "certificate-path" => { + let settings = self.settings.lock().unwrap(); + let certpath = settings.certificate_path.as_ref(); + certpath.and_then(|file| file.to_str()).to_value() + } + "use-datagram" => { + let settings = self.settings.lock().unwrap(); + settings.use_datagram.to_value() + } + "private-key-type" => { + let settings = self.settings.lock().unwrap(); + settings.private_key_type.to_value() + } + _ => unimplemented!(), + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for QuicSrc { + const NAME: &'static str = "GstQUICSrc"; + type Type = super::QuicSrc; + type ParentType = gst_base::BaseSrc; +} + +impl BaseSrcImpl for QuicSrc { + fn is_seekable(&self) -> bool { + false + } + + fn start(&self) -> Result<(), gst::ErrorMessage> { + let settings = self.settings.lock().unwrap(); + let timeout = settings.timeout; + drop(settings); + + let mut state = self.state.lock().unwrap(); + + if let State::Started { .. } = *state { + unreachable!("QuicSrc already started"); + } + + match wait(&self.canceller, self.wait_for_connection(), timeout) { + Ok(Ok((c, s))) => { + *state = State::Started(Started { + connection: c, + stream: s, + }); + + gst::info!(CAT, imp: self, "Started"); + + Ok(()) + } + Ok(Err(e)) | Err(e) => match e { + WaitError::FutureAborted => { + gst::warning!(CAT, imp: self, "Connection aborted"); + Ok(()) + } + WaitError::FutureError(err) => { + gst::error!(CAT, imp: self, "Connection request failed: {}", err); + Err(gst::error_msg!( + gst::ResourceError::Failed, + ["Connection request failed: {}", err] + )) + } + }, + } + } + + fn stop(&self) -> Result<(), gst::ErrorMessage> { + self.cancel(); + + let mut state = self.state.lock().unwrap(); + + if let State::Started(ref mut state) = *state { + let connection = &state.connection; + + connection.close( + CONNECTION_CLOSE_CODE.into(), + CONNECTION_CLOSE_MSG.as_bytes(), + ); + } + + *state = State::Stopped; + + Ok(()) + } + + fn query(&self, query: &mut gst::QueryRef) -> bool { + if let gst::QueryViewMut::Scheduling(q) = query.view_mut() { + q.set( + gst::SchedulingFlags::SEQUENTIAL | gst::SchedulingFlags::BANDWIDTH_LIMITED, + 1, + -1, + 0, + ); + q.add_scheduling_modes(&[gst::PadMode::Pull, gst::PadMode::Push]); + return true; + } + + BaseSrcImplExt::parent_query(self, query) + } + + fn create( + &self, + offset: u64, + buffer: Option<&mut gst::BufferRef>, + length: u32, + ) -> Result { + let data = self.get(offset, u64::from(length)); + + match data { + Ok(bytes) => { + if bytes.is_empty() { + gst::debug!(CAT, imp: self, "End of stream"); + return Err(gst::FlowError::Eos); + } + + if let Some(buffer) = buffer { + if let Err(copied_bytes) = buffer.copy_from_slice(0, bytes.as_ref()) { + buffer.set_size(copied_bytes); + } + Ok(CreateSuccess::FilledBuffer) + } else { + Ok(CreateSuccess::NewBuffer(gst::Buffer::from_slice(bytes))) + } + } + Err(None) => Err(gst::FlowError::Flushing), + Err(Some(err)) => { + gst::error!(CAT, imp: self, "Could not GET: {}", err); + Err(gst::FlowError::Error) + } + } + } + + fn unlock(&self) -> Result<(), gst::ErrorMessage> { + self.cancel(); + Ok(()) + } + + fn caps(&self, filter: Option<&gst::Caps>) -> Option { + let settings = self.settings.lock().unwrap(); + + let mut tmp_caps = settings.caps.clone(); + + gst::debug!(CAT, imp: self, "Advertising our own caps: {:?}", &tmp_caps); + + if let Some(filter_caps) = filter { + gst::debug!( + CAT, + imp: self, + "Intersecting with filter caps: {:?}", + &filter_caps + ); + + tmp_caps = filter_caps.intersect_with_mode(&tmp_caps, gst::CapsIntersectMode::First); + }; + + gst::debug!(CAT, imp: self, "Returning caps: {:?}", &tmp_caps); + + Some(tmp_caps) + } +} + +impl QuicSrc { + fn get(&self, _offset: u64, length: u64) -> Result> { + let settings = self.settings.lock().unwrap(); + let timeout = settings.timeout; + let use_datagram = settings.use_datagram; + drop(settings); + + let mut state = self.state.lock().unwrap(); + + let (conn, stream) = match *state { + State::Started(Started { + ref connection, + ref mut stream, + }) => (connection, stream), + State::Stopped => { + return Err(Some(gst::error_msg!( + gst::LibraryError::Failed, + ["Cannot get data before start"] + ))); + } + }; + + let future = async { + if use_datagram { + match conn.read_datagram().await { + Ok(bytes) => Ok(bytes), + Err(err) => match err { + ConnectionError::ApplicationClosed(_) + | ConnectionError::ConnectionClosed(_) => Ok(Bytes::new()), + _ => Err(WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Datagram read error: {}", err] + ))), + }, + } + } else { + let recv = stream.as_mut().unwrap(); + + match recv.read_chunk(length as usize, true).await { + Ok(Some(chunk)) => Ok(chunk.bytes), + Ok(None) => Ok(Bytes::new()), + Err(err) => Err(WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Stream read error: {}", err] + ))), + } + } + }; + + match wait(&self.canceller, future, timeout) { + Ok(Ok(bytes)) => Ok(bytes), + Ok(Err(e)) | Err(e) => match e { + WaitError::FutureAborted => { + gst::warning!(CAT, imp: self, "Read from stream request aborted"); + Err(None) + } + WaitError::FutureError(e) => { + gst::error!(CAT, imp: self, "Failed to read from stream: {}", e); + Err(Some(e)) + } + }, + } + } + + fn cancel(&self) { + let mut canceller = self.canceller.lock().unwrap(); + + if let Some(c) = canceller.take() { + c.abort() + }; + } + + async fn wait_for_connection(&self) -> Result<(Connection, Option), WaitError> { + let server_addr; + let server_name; + let use_datagram; + let secure_conn; + let cert_path; + let private_key_type; + + { + let settings = self.settings.lock().unwrap(); + server_addr = settings.server_address; + server_name = settings.server_name.clone(); + use_datagram = settings.use_datagram; + secure_conn = settings.secure_conn; + cert_path = settings.certificate_path.clone(); + private_key_type = settings.private_key_type; + } + + let endpoint = server_endpoint( + server_addr, + &server_name, + secure_conn, + cert_path, + private_key_type, + ) + .map_err(|err| { + WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Failed to configure endpoint: {}", err] + )) + })?; + + let incoming_conn = endpoint.accept().await.unwrap(); + + let connection = incoming_conn.await.map_err(|err| { + WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Connection error: {}", err] + )) + })?; + + let stream = if !use_datagram { + let res = connection.accept_uni().await.map_err(|err| { + WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Failed to open stream: {}", err] + )) + })?; + + Some(res) + } else { + None + }; + + gst::info!( + CAT, + imp: self, + "Remote connection accepted: {}", + connection.remote_address() + ); + + Ok((connection, stream)) + } +} diff --git a/net/quic/src/quicsrc/mod.rs b/net/quic/src/quicsrc/mod.rs new file mode 100644 index 00000000..2eca4854 --- /dev/null +++ b/net/quic/src/quicsrc/mod.rs @@ -0,0 +1,36 @@ +// Copyright (C) 2024, Asymptotic Inc. +// Author: Sanchayan Maity +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use gst::glib; +use gst::prelude::*; + +mod imp; + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)] +#[repr(u32)] +#[enum_type(name = "GstQuicPrivateKeyType")] +pub enum QuicPrivateKeyType { + #[enum_value(name = "PKCS8: PKCS #8 Private Key.", nick = "pkcs8")] + Pkcs8, + #[enum_value(name = "RSA: RSA Private Key.", nick = "rsa")] + Rsa, +} + +glib::wrapper! { + pub struct QuicSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; +} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "quicsrc", + gst::Rank::MARGINAL, + QuicSrc::static_type(), + ) +} diff --git a/net/quic/src/utils.rs b/net/quic/src/utils.rs new file mode 100644 index 00000000..0932408a --- /dev/null +++ b/net/quic/src/utils.rs @@ -0,0 +1,252 @@ +// Copyright (C) 2024, Asymptotic Inc. +// Author: Sanchayan Maity +//G +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use crate::quicsrc::QuicPrivateKeyType; +use futures::future; +use futures::prelude::*; +use gst::ErrorMessage; +use once_cell::sync::Lazy; +use quinn::{ClientConfig, Endpoint, ServerConfig}; +use std::error::Error; +use std::fs::File; +use std::io::BufReader; +use std::net::{AddrParseError, SocketAddr}; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use thiserror::Error; +use tokio::runtime; + +pub const CONNECTION_CLOSE_CODE: u32 = 0; +pub const CONNECTION_CLOSE_MSG: &str = "Stopped"; + +#[derive(Error, Debug)] +pub enum WaitError { + #[error("Future aborted")] + FutureAborted, + #[error("Future returned an error: {0}")] + FutureError(ErrorMessage), +} + +pub static RUNTIME: Lazy = Lazy::new(|| { + runtime::Builder::new_multi_thread() + .enable_all() + .worker_threads(1) + .thread_name("gst-quic-runtime") + .build() + .unwrap() +}); + +pub fn wait( + canceller: &Mutex>, + future: F, + timeout: u32, +) -> Result +where + F: Send + Future, + T: Send + 'static, +{ + let mut canceller_guard = canceller.lock().unwrap(); + let (abort_handle, abort_registration) = future::AbortHandle::new_pair(); + + if canceller_guard.is_some() { + return Err(WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Old Canceller should not exist"] + ))); + } + + canceller_guard.replace(abort_handle); + drop(canceller_guard); + + let future = async { + if timeout == 0 { + Ok(future.await) + } else { + let res = tokio::time::timeout(Duration::from_secs(timeout.into()), future).await; + + match res { + Ok(r) => Ok(r), + Err(e) => Err(gst::error_msg!( + gst::ResourceError::Read, + ["Request timeout, elapsed: {}", e.to_string()] + )), + } + } + }; + + let future = async { + match future::Abortable::new(future, abort_registration).await { + Ok(Ok(res)) => Ok(res), + + Ok(Err(err)) => Err(WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Future resolved with an error {:?}", err] + ))), + + Err(future::Aborted) => Err(WaitError::FutureAborted), + } + }; + + let res = RUNTIME.block_on(future); + + canceller_guard = canceller.lock().unwrap(); + *canceller_guard = None; + + res +} + +/* + * Following functions are taken from Quinn documentation/repository + */ +pub fn make_socket_addr(addr: &str) -> Result { + addr.parse::() +} + +struct SkipServerVerification; + +impl SkipServerVerification { + pub fn new() -> Arc { + Arc::new(Self) + } +} + +impl rustls::client::ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _server_name: &rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: std::time::SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} + +fn configure_client(secure_conn: bool) -> Result> { + if secure_conn { + Ok(ClientConfig::with_native_roots()) + } else { + let crypto = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_custom_certificate_verifier(SkipServerVerification::new()) + .with_no_client_auth(); + + Ok(ClientConfig::new(Arc::new(crypto))) + } +} + +fn read_certs_from_file( + certificate_path: Option, + private_key_type: QuicPrivateKeyType, +) -> Result<(Vec, rustls::PrivateKey), Box> { + /* + * NOTE: + * + * Certificate file here should correspond to fullchain.pem where + * fullchain.pem = cert.pem + chain.pem. + * fullchain.pem DOES NOT include a CA's Root Certificates. + * + * One typically uses chain.pem (or the first certificate in it) when asked + * for a CA bundle or CA certificate. + * + * One typically uses fullchain.pem when asked for the entire certificate + * chain in a single file. For example, this is the case of modern day + * Apache and nginx. + */ + let cert_file = certificate_path + .clone() + .expect("Expected path to certificates be valid") + .join("fullchain.pem"); + let key_file = certificate_path + .expect("Expected path to certificates be valid") + .join("privkey.pem"); + + let certs: Vec = { + let cert_file = File::open(cert_file.as_path())?; + let mut cert_file_rdr = BufReader::new(cert_file); + let cert_vec = rustls_pemfile::certs(&mut cert_file_rdr)?; + cert_vec.into_iter().map(rustls::Certificate).collect() + }; + + let key: rustls::PrivateKey = { + let key_file = File::open(key_file.as_path())?; + let mut key_file_rdr = BufReader::new(key_file); + let mut key_vec; + + // If the file starts with "BEGIN RSA PRIVATE KEY" + if let QuicPrivateKeyType::Rsa = private_key_type { + key_vec = rustls_pemfile::rsa_private_keys(&mut key_file_rdr)?; + } else { + // If the file starts with "BEGIN PRIVATE KEY" + key_vec = rustls_pemfile::pkcs8_private_keys(&mut key_file_rdr)?; + } + + assert_eq!(key_vec.len(), 1); + + rustls::PrivateKey(key_vec.remove(0)) + }; + + Ok((certs, key)) +} + +fn configure_server( + server_name: &str, + secure_conn: bool, + certificate_path: Option, + private_key_type: QuicPrivateKeyType, +) -> Result<(ServerConfig, Vec), Box> { + let (cert, key) = if secure_conn { + read_certs_from_file(certificate_path, private_key_type).unwrap() + } else { + let cert = rcgen::generate_simple_self_signed(vec![server_name.into()]).unwrap(); + let cert_der = cert.serialize_der().unwrap(); + let priv_key = cert.serialize_private_key_der(); + let priv_key = rustls::PrivateKey(priv_key); + let cert_chain = vec![rustls::Certificate(cert_der)]; + + (cert_chain, priv_key) + }; + + let mut server_config = ServerConfig::with_single_cert(cert.clone(), key)?; + Arc::get_mut(&mut server_config.transport) + .unwrap() + .max_concurrent_bidi_streams(0_u8.into()) + .max_concurrent_uni_streams(1_u8.into()); + + Ok((server_config, cert)) +} + +pub fn server_endpoint( + server_addr: SocketAddr, + server_name: &str, + secure_conn: bool, + certificate_path: Option, + private_key_type: QuicPrivateKeyType, +) -> Result> { + let (server_config, _) = + configure_server(server_name, secure_conn, certificate_path, private_key_type)?; + let endpoint = Endpoint::server(server_config, server_addr)?; + + Ok(endpoint) +} + +pub fn client_endpoint( + client_addr: SocketAddr, + secure_conn: bool, +) -> Result> { + let client_cfg = configure_client(secure_conn)?; + let mut endpoint = Endpoint::client(client_addr)?; + + endpoint.set_default_client_config(client_cfg); + + Ok(endpoint) +} diff --git a/net/quic/tests/quic.rs b/net/quic/tests/quic.rs new file mode 100644 index 00000000..11be17ac --- /dev/null +++ b/net/quic/tests/quic.rs @@ -0,0 +1,111 @@ +// Copyright (C) 2024, Asymptotic Inc. +// Author: Sanchayan Maity +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use gst::prelude::*; +use serial_test::serial; +use std::thread; + +fn init() { + use std::sync::Once; + static INIT: Once = Once::new(); + + INIT.call_once(|| { + gst::init().unwrap(); + gstquic::plugin_register_static().expect("QUIC source sink send receive tests"); + }); +} + +fn make_buffer(content: &[u8]) -> gst::Buffer { + let mut buf = gst::Buffer::from_slice(content.to_owned()); + buf.make_mut().set_pts(gst::ClockTime::from_mseconds(200)); + buf +} + +#[test] +#[serial] +fn test_send_receive_without_datagram() { + init(); + + let content = "Hello, world!\n".as_bytes(); + + thread::spawn(move || { + let mut h1 = gst_check::Harness::new("quicsink"); + + h1.set_src_caps(gst::Caps::builder("text/plain").build()); + + h1.play(); + + assert!(h1.push(make_buffer(content)) == Ok(gst::FlowSuccess::Ok)); + + h1.push_event(gst::event::Eos::new()); + + h1.element().unwrap().set_state(gst::State::Null).unwrap(); + + drop(h1); + }); + + let mut h2 = gst_check::Harness::new("quicsrc"); + + h2.play(); + + let buf = h2.pull_until_eos().unwrap().unwrap(); + + assert_eq!( + content, + buf.into_mapped_buffer_readable().unwrap().as_slice() + ); + + h2.element().unwrap().set_state(gst::State::Null).unwrap(); + + drop(h2); +} + +#[test] +#[serial] +fn test_send_receive_with_datagram() { + init(); + + let content = "Hello, world!\n".as_bytes(); + + // Use a different port address compared to the default that will be used + // in the other test. We get a address already in use error otherwise. + thread::spawn(move || { + let mut h1 = gst_check::Harness::new_empty(); + + h1.add_parse(format!("quicsrc use-datagram=true server-address=127.0.0.1:6000").as_str()); + h1.play(); + + let buf = h1.pull_until_eos().unwrap().unwrap(); + + assert_eq!( + content, + buf.into_mapped_buffer_readable().unwrap().as_slice() + ); + + h1.element().unwrap().set_state(gst::State::Null).unwrap(); + + drop(h1); + }); + + let mut h2 = gst_check::Harness::new_empty(); + + h2.add_parse(format!("quicsink use-datagram=true client-address=127.0.0.1:6001 server-address=127.0.0.1:6000").as_str()); + + h2.set_src_caps(gst::Caps::builder("text/plain").build()); + + h2.play(); + + assert!(h2.push(make_buffer(content)) == Ok(gst::FlowSuccess::Ok)); + + h2.push_event(gst::event::Eos::new()); + + h2.element().unwrap().set_state(gst::State::Null).unwrap(); + + drop(h2); +} From 75b25d011fde05ffbfb83e4f478d48ad0a37d56c Mon Sep 17 00:00:00 2001 From: Tamas Levai Date: Mon, 12 Feb 2024 20:14:33 +0100 Subject: [PATCH 02/12] net/quic: Allow specifying an ALPN transport parameter See https://datatracker.ietf.org/doc/html/rfc9000#section-7.4. This controls the Transport Layer Security (TLS) extension for application-layer protocol negotiation within the TLS handshake. Part-of: --- net/quic/src/quicsink/imp.rs | 19 ++++++++++++++++++- net/quic/src/quicsrc/imp.rs | 18 ++++++++++++++++++ net/quic/src/utils.rs | 23 ++++++++++++++++++----- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/net/quic/src/quicsink/imp.rs b/net/quic/src/quicsink/imp.rs index ee53b804..382985cc 100644 --- a/net/quic/src/quicsink/imp.rs +++ b/net/quic/src/quicsink/imp.rs @@ -22,6 +22,7 @@ use std::sync::Mutex; static DEFAULT_SERVER_NAME: &str = "localhost"; static DEFAULT_SERVER_ADDR: &str = "127.0.0.1:5000"; static DEFAULT_CLIENT_ADDR: &str = "127.0.0.1:5001"; +const DEFAULT_ALPN: &str = "h3"; const DEFAULT_TIMEOUT: u32 = 15; const DEFAULT_SECURE_CONNECTION: bool = true; @@ -46,6 +47,7 @@ struct Settings { client_address: SocketAddr, server_address: SocketAddr, server_name: String, + alpn: String, timeout: u32, secure_conn: bool, use_datagram: bool, @@ -57,6 +59,7 @@ impl Default for Settings { client_address: DEFAULT_CLIENT_ADDR.parse::().unwrap(), server_address: DEFAULT_SERVER_ADDR.parse::().unwrap(), server_name: DEFAULT_SERVER_NAME.to_string(), + alpn: DEFAULT_ALPN.to_string(), timeout: DEFAULT_TIMEOUT, secure_conn: DEFAULT_SECURE_CONNECTION, use_datagram: false, @@ -132,6 +135,10 @@ impl ObjectImpl for QuicSink { .nick("QUIC client address") .blurb("Address to be used by this QUIC client e.g. 127.0.0.1:5001") .build(), + glib::ParamSpecString::builder("alpn") + .nick("QUIC ALPN value") + .blurb("QUIC connection Application-Layer Protocol Negotiation (ALPN) value") + .build(), glib::ParamSpecUInt::builder("timeout") .nick("Timeout") .blurb("Value in seconds to timeout QUIC endpoint requests (0 = No timeout).") @@ -191,6 +198,10 @@ impl ObjectImpl for QuicSink { ), } } + "alpn" => { + let mut settings = self.settings.lock().unwrap(); + settings.alpn = value.get::().expect("type checked upstream"); + } "timeout" => { let mut settings = self.settings.lock().unwrap(); settings.timeout = value.get().expect("type checked upstream"); @@ -221,6 +232,10 @@ impl ObjectImpl for QuicSink { let settings = self.settings.lock().unwrap(); settings.client_address.to_string().to_value() } + "alpn" => { + let settings = self.settings.lock().unwrap(); + settings.alpn.to_value() + } "timeout" => { let settings = self.settings.lock().unwrap(); settings.timeout.to_value() @@ -424,6 +439,7 @@ impl QuicSink { let client_addr; let server_addr; let server_name; + let alpn; let use_datagram; let secure_conn; @@ -432,11 +448,12 @@ impl QuicSink { client_addr = settings.client_address; server_addr = settings.server_address; server_name = settings.server_name.clone(); + alpn = settings.alpn.clone(); use_datagram = settings.use_datagram; secure_conn = settings.secure_conn; } - let endpoint = client_endpoint(client_addr, secure_conn).map_err(|err| { + let endpoint = client_endpoint(client_addr, secure_conn, &alpn).map_err(|err| { WaitError::FutureError(gst::error_msg!( gst::ResourceError::Failed, ["Failed to configure endpoint: {}", err] diff --git a/net/quic/src/quicsrc/imp.rs b/net/quic/src/quicsrc/imp.rs index 3dc666a3..38e6ea9a 100644 --- a/net/quic/src/quicsrc/imp.rs +++ b/net/quic/src/quicsrc/imp.rs @@ -25,6 +25,7 @@ use std::sync::Mutex; static DEFAULT_SERVER_NAME: &str = "localhost"; static DEFAULT_SERVER_ADDR: &str = "127.0.0.1:5000"; +const DEFAULT_ALPN: &str = "h3"; const DEFAULT_TIMEOUT: u32 = 15; const DEFAULT_PRIVATE_KEY_TYPE: QuicPrivateKeyType = QuicPrivateKeyType::Pkcs8; const DEFAULT_SECURE_CONNECTION: bool = true; @@ -53,6 +54,7 @@ enum State { struct Settings { server_address: SocketAddr, server_name: String, + alpn: String, timeout: u32, secure_conn: bool, caps: gst::Caps, @@ -66,6 +68,7 @@ impl Default for Settings { Settings { server_address: DEFAULT_SERVER_ADDR.parse::().unwrap(), server_name: DEFAULT_SERVER_NAME.to_string(), + alpn: DEFAULT_ALPN.to_string(), timeout: DEFAULT_TIMEOUT, secure_conn: DEFAULT_SECURE_CONNECTION, caps: gst::Caps::new_any(), @@ -167,6 +170,10 @@ impl ObjectImpl for QuicSrc { .nick("QUIC server address") .blurb("Address of the QUIC server to connect to e.g. 127.0.0.1:5000") .build(), + glib::ParamSpecString::builder("alpn") + .nick("QUIC ALPN value") + .blurb("QUIC connection Application-Layer Protocol Negotiation (ALPN) value") + .build(), glib::ParamSpecUInt::builder("timeout") .nick("Timeout") .blurb("Value in seconds to timeout QUIC endpoint requests (0 = No timeout).") @@ -223,6 +230,10 @@ impl ObjectImpl for QuicSrc { ), } } + "alpn" => { + let mut settings = self.settings.lock().unwrap(); + settings.alpn = value.get::().expect("type checked upstream"); + } "caps" => { let mut settings = self.settings.lock().unwrap(); settings.caps = value @@ -270,6 +281,10 @@ impl ObjectImpl for QuicSrc { let settings = self.settings.lock().unwrap(); settings.server_address.to_string().to_value() } + "alpn" => { + let settings = self.settings.lock().unwrap(); + settings.alpn.to_value() + } "caps" => { let settings = self.settings.lock().unwrap(); settings.caps.to_value() @@ -520,6 +535,7 @@ impl QuicSrc { async fn wait_for_connection(&self) -> Result<(Connection, Option), WaitError> { let server_addr; let server_name; + let alpn; let use_datagram; let secure_conn; let cert_path; @@ -529,6 +545,7 @@ impl QuicSrc { let settings = self.settings.lock().unwrap(); server_addr = settings.server_address; server_name = settings.server_name.clone(); + alpn = settings.alpn.clone(); use_datagram = settings.use_datagram; secure_conn = settings.secure_conn; cert_path = settings.certificate_path.clone(); @@ -539,6 +556,7 @@ impl QuicSrc { server_addr, &server_name, secure_conn, + &alpn, cert_path, private_key_type, ) diff --git a/net/quic/src/utils.rs b/net/quic/src/utils.rs index 0932408a..18ff2094 100644 --- a/net/quic/src/utils.rs +++ b/net/quic/src/utils.rs @@ -131,14 +131,15 @@ impl rustls::client::ServerCertVerifier for SkipServerVerification { } } -fn configure_client(secure_conn: bool) -> Result> { +fn configure_client(secure_conn: bool, alpn: &str) -> Result> { if secure_conn { Ok(ClientConfig::with_native_roots()) } else { - let crypto = rustls::ClientConfig::builder() + let mut crypto = rustls::ClientConfig::builder() .with_safe_defaults() .with_custom_certificate_verifier(SkipServerVerification::new()) .with_no_client_auth(); + crypto.alpn_protocols = vec![alpn.as_bytes().to_vec()]; Ok(ClientConfig::new(Arc::new(crypto))) } @@ -201,6 +202,7 @@ fn read_certs_from_file( fn configure_server( server_name: &str, secure_conn: bool, + alpn: &str, certificate_path: Option, private_key_type: QuicPrivateKeyType, ) -> Result<(ServerConfig, Vec), Box> { @@ -216,7 +218,16 @@ fn configure_server( (cert_chain, priv_key) }; - let mut server_config = ServerConfig::with_single_cert(cert.clone(), key)?; + let mut crypto = rustls::ServerConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap() + .with_no_client_auth() + .with_single_cert(cert.clone(), key)?; + crypto.alpn_protocols = vec![alpn.as_bytes().to_vec()]; + let mut server_config = ServerConfig::with_crypto(Arc::new(crypto)); + Arc::get_mut(&mut server_config.transport) .unwrap() .max_concurrent_bidi_streams(0_u8.into()) @@ -229,11 +240,12 @@ pub fn server_endpoint( server_addr: SocketAddr, server_name: &str, secure_conn: bool, + alpn: &str, certificate_path: Option, private_key_type: QuicPrivateKeyType, ) -> Result> { let (server_config, _) = - configure_server(server_name, secure_conn, certificate_path, private_key_type)?; + configure_server(server_name, secure_conn, alpn, certificate_path, private_key_type)?; let endpoint = Endpoint::server(server_config, server_addr)?; Ok(endpoint) @@ -242,8 +254,9 @@ pub fn server_endpoint( pub fn client_endpoint( client_addr: SocketAddr, secure_conn: bool, + alpn: &str, ) -> Result> { - let client_cfg = configure_client(secure_conn)?; + let client_cfg = configure_client(secure_conn, alpn)?; let mut endpoint = Endpoint::client(client_addr)?; endpoint.set_default_client_config(client_cfg); From ce930eab5fe4b1c69bffc9a8ec34f2156411829c Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 14 Feb 2024 13:08:37 +0530 Subject: [PATCH 03/12] net/quic: Allow setting multiple ALPN transport parameters For reference, see https://datatracker.ietf.org/doc/html/rfc9000#section-7.4 https://datatracker.ietf.org/doc/html/rfc7301 Part-of: --- net/quic/src/quicsink/imp.rs | 44 ++++++++++++++++++++++++++---------- net/quic/src/quicsrc/imp.rs | 44 ++++++++++++++++++++++++++---------- net/quic/src/utils.rs | 29 +++++++++++++++++------- 3 files changed, 85 insertions(+), 32 deletions(-) diff --git a/net/quic/src/quicsink/imp.rs b/net/quic/src/quicsink/imp.rs index 382985cc..95eb2cf0 100644 --- a/net/quic/src/quicsink/imp.rs +++ b/net/quic/src/quicsink/imp.rs @@ -22,6 +22,14 @@ use std::sync::Mutex; static DEFAULT_SERVER_NAME: &str = "localhost"; static DEFAULT_SERVER_ADDR: &str = "127.0.0.1:5000"; static DEFAULT_CLIENT_ADDR: &str = "127.0.0.1:5001"; +/* + * For QUIC transport parameters + * + * + * A HTTP client might specify "http/1.1" and/or "h2" or "h3". + * Other well-known values are listed in the at IANA registry at + * . + */ const DEFAULT_ALPN: &str = "h3"; const DEFAULT_TIMEOUT: u32 = 15; const DEFAULT_SECURE_CONNECTION: bool = true; @@ -47,7 +55,7 @@ struct Settings { client_address: SocketAddr, server_address: SocketAddr, server_name: String, - alpn: String, + alpns: Vec, timeout: u32, secure_conn: bool, use_datagram: bool, @@ -59,7 +67,7 @@ impl Default for Settings { client_address: DEFAULT_CLIENT_ADDR.parse::().unwrap(), server_address: DEFAULT_SERVER_ADDR.parse::().unwrap(), server_name: DEFAULT_SERVER_NAME.to_string(), - alpn: DEFAULT_ALPN.to_string(), + alpns: vec![DEFAULT_ALPN.to_string()], timeout: DEFAULT_TIMEOUT, secure_conn: DEFAULT_SECURE_CONNECTION, use_datagram: false, @@ -135,9 +143,10 @@ impl ObjectImpl for QuicSink { .nick("QUIC client address") .blurb("Address to be used by this QUIC client e.g. 127.0.0.1:5001") .build(), - glib::ParamSpecString::builder("alpn") - .nick("QUIC ALPN value") - .blurb("QUIC connection Application-Layer Protocol Negotiation (ALPN) value") + gst::ParamSpecArray::builder("alpn-protocols") + .nick("QUIC ALPN values") + .blurb("QUIC connection Application-Layer Protocol Negotiation (ALPN) values") + .element_spec(&glib::ParamSpecString::builder("alpn-protocol").build()) .build(), glib::ParamSpecUInt::builder("timeout") .nick("Timeout") @@ -198,9 +207,19 @@ impl ObjectImpl for QuicSink { ), } } - "alpn" => { + "alpn-protocols" => { let mut settings = self.settings.lock().unwrap(); - settings.alpn = value.get::().expect("type checked upstream"); + settings.alpns = value + .get::() + .expect("type checked upstream") + .as_slice() + .iter() + .map(|alpn| { + alpn.get::<&str>() + .expect("type checked upstream") + .to_string() + }) + .collect::>(); } "timeout" => { let mut settings = self.settings.lock().unwrap(); @@ -232,9 +251,10 @@ impl ObjectImpl for QuicSink { let settings = self.settings.lock().unwrap(); settings.client_address.to_string().to_value() } - "alpn" => { + "alpn-protocols" => { let settings = self.settings.lock().unwrap(); - settings.alpn.to_value() + let alpns = settings.alpns.iter().map(|v| v.as_str()); + gst::Array::new(alpns).to_value() } "timeout" => { let settings = self.settings.lock().unwrap(); @@ -439,7 +459,7 @@ impl QuicSink { let client_addr; let server_addr; let server_name; - let alpn; + let alpns; let use_datagram; let secure_conn; @@ -448,12 +468,12 @@ impl QuicSink { client_addr = settings.client_address; server_addr = settings.server_address; server_name = settings.server_name.clone(); - alpn = settings.alpn.clone(); + alpns = settings.alpns.clone(); use_datagram = settings.use_datagram; secure_conn = settings.secure_conn; } - let endpoint = client_endpoint(client_addr, secure_conn, &alpn).map_err(|err| { + let endpoint = client_endpoint(client_addr, secure_conn, alpns).map_err(|err| { WaitError::FutureError(gst::error_msg!( gst::ResourceError::Failed, ["Failed to configure endpoint: {}", err] diff --git a/net/quic/src/quicsrc/imp.rs b/net/quic/src/quicsrc/imp.rs index 38e6ea9a..8fc48803 100644 --- a/net/quic/src/quicsrc/imp.rs +++ b/net/quic/src/quicsrc/imp.rs @@ -25,6 +25,14 @@ use std::sync::Mutex; static DEFAULT_SERVER_NAME: &str = "localhost"; static DEFAULT_SERVER_ADDR: &str = "127.0.0.1:5000"; +/* + * For QUIC transport parameters + * + * + * A HTTP client might specify "http/1.1" and/or "h2" or "h3". + * Other well-known values are listed in the at IANA registry at + * . + */ const DEFAULT_ALPN: &str = "h3"; const DEFAULT_TIMEOUT: u32 = 15; const DEFAULT_PRIVATE_KEY_TYPE: QuicPrivateKeyType = QuicPrivateKeyType::Pkcs8; @@ -54,7 +62,7 @@ enum State { struct Settings { server_address: SocketAddr, server_name: String, - alpn: String, + alpns: Vec, timeout: u32, secure_conn: bool, caps: gst::Caps, @@ -68,7 +76,7 @@ impl Default for Settings { Settings { server_address: DEFAULT_SERVER_ADDR.parse::().unwrap(), server_name: DEFAULT_SERVER_NAME.to_string(), - alpn: DEFAULT_ALPN.to_string(), + alpns: vec![DEFAULT_ALPN.to_string()], timeout: DEFAULT_TIMEOUT, secure_conn: DEFAULT_SECURE_CONNECTION, caps: gst::Caps::new_any(), @@ -170,9 +178,10 @@ impl ObjectImpl for QuicSrc { .nick("QUIC server address") .blurb("Address of the QUIC server to connect to e.g. 127.0.0.1:5000") .build(), - glib::ParamSpecString::builder("alpn") - .nick("QUIC ALPN value") - .blurb("QUIC connection Application-Layer Protocol Negotiation (ALPN) value") + gst::ParamSpecArray::builder("alpn-protocols") + .nick("QUIC ALPN values") + .blurb("QUIC connection Application-Layer Protocol Negotiation (ALPN) values") + .element_spec(&glib::ParamSpecString::builder("alpn-protocol").build()) .build(), glib::ParamSpecUInt::builder("timeout") .nick("Timeout") @@ -230,9 +239,19 @@ impl ObjectImpl for QuicSrc { ), } } - "alpn" => { + "alpn-protocols" => { let mut settings = self.settings.lock().unwrap(); - settings.alpn = value.get::().expect("type checked upstream"); + settings.alpns = value + .get::() + .expect("type checked upstream") + .as_slice() + .iter() + .map(|alpn| { + alpn.get::<&str>() + .expect("type checked upstream") + .to_string() + }) + .collect::>() } "caps" => { let mut settings = self.settings.lock().unwrap(); @@ -281,9 +300,10 @@ impl ObjectImpl for QuicSrc { let settings = self.settings.lock().unwrap(); settings.server_address.to_string().to_value() } - "alpn" => { + "alpn-protocols" => { let settings = self.settings.lock().unwrap(); - settings.alpn.to_value() + let alpns = settings.alpns.iter().map(|v| v.as_str()); + gst::Array::new(alpns).to_value() } "caps" => { let settings = self.settings.lock().unwrap(); @@ -535,7 +555,7 @@ impl QuicSrc { async fn wait_for_connection(&self) -> Result<(Connection, Option), WaitError> { let server_addr; let server_name; - let alpn; + let alpns; let use_datagram; let secure_conn; let cert_path; @@ -545,7 +565,7 @@ impl QuicSrc { let settings = self.settings.lock().unwrap(); server_addr = settings.server_address; server_name = settings.server_name.clone(); - alpn = settings.alpn.clone(); + alpns = settings.alpns.clone(); use_datagram = settings.use_datagram; secure_conn = settings.secure_conn; cert_path = settings.certificate_path.clone(); @@ -556,7 +576,7 @@ impl QuicSrc { server_addr, &server_name, secure_conn, - &alpn, + alpns, cert_path, private_key_type, ) diff --git a/net/quic/src/utils.rs b/net/quic/src/utils.rs index 18ff2094..c9da4a3f 100644 --- a/net/quic/src/utils.rs +++ b/net/quic/src/utils.rs @@ -131,7 +131,7 @@ impl rustls::client::ServerCertVerifier for SkipServerVerification { } } -fn configure_client(secure_conn: bool, alpn: &str) -> Result> { +fn configure_client(secure_conn: bool, alpns: Vec) -> Result> { if secure_conn { Ok(ClientConfig::with_native_roots()) } else { @@ -139,7 +139,11 @@ fn configure_client(secure_conn: bool, alpn: &str) -> Result> = alpns + .iter() + .map(|x| x.as_bytes().to_vec()) + .collect::>(); + crypto.alpn_protocols = alpn_protocols; Ok(ClientConfig::new(Arc::new(crypto))) } @@ -202,8 +206,8 @@ fn read_certs_from_file( fn configure_server( server_name: &str, secure_conn: bool, - alpn: &str, certificate_path: Option, + alpns: Vec, private_key_type: QuicPrivateKeyType, ) -> Result<(ServerConfig, Vec), Box> { let (cert, key) = if secure_conn { @@ -225,7 +229,11 @@ fn configure_server( .unwrap() .with_no_client_auth() .with_single_cert(cert.clone(), key)?; - crypto.alpn_protocols = vec![alpn.as_bytes().to_vec()]; + let alpn_protocols: Vec> = alpns + .iter() + .map(|x| x.as_bytes().to_vec()) + .collect::>(); + crypto.alpn_protocols = alpn_protocols; let mut server_config = ServerConfig::with_crypto(Arc::new(crypto)); Arc::get_mut(&mut server_config.transport) @@ -240,12 +248,17 @@ pub fn server_endpoint( server_addr: SocketAddr, server_name: &str, secure_conn: bool, - alpn: &str, + alpns: Vec, certificate_path: Option, private_key_type: QuicPrivateKeyType, ) -> Result> { - let (server_config, _) = - configure_server(server_name, secure_conn, alpn, certificate_path, private_key_type)?; + let (server_config, _) = configure_server( + server_name, + secure_conn, + certificate_path, + alpns, + private_key_type, + )?; let endpoint = Endpoint::server(server_config, server_addr)?; Ok(endpoint) @@ -254,7 +267,7 @@ pub fn server_endpoint( pub fn client_endpoint( client_addr: SocketAddr, secure_conn: bool, - alpn: &str, + alpn: Vec, ) -> Result> { let client_cfg = configure_client(secure_conn, alpn)?; let mut endpoint = Endpoint::client(client_addr)?; From befd8d4bd2d4044f3738b7277d2bbda37cc2f63a Mon Sep 17 00:00:00 2001 From: Tamas Levai Date: Wed, 21 Feb 2024 11:37:40 +0100 Subject: [PATCH 04/12] net/quic: Allow SSL keylog file for debugging rustls has a KeyLog implementation that opens a file whose name is given by the `SSLKEYLOGFILE` environment variable, and writes keys into it. If SSLKEYLOGFILE is not set, this does nothing. See https://docs.rs/rustls/latest/rustls/struct.KeyLogFile.html https://docs.rs/rustls/latest/rustls/trait.KeyLog.html Part-of: --- net/quic/src/utils.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/quic/src/utils.rs b/net/quic/src/utils.rs index c9da4a3f..1d2ab185 100644 --- a/net/quic/src/utils.rs +++ b/net/quic/src/utils.rs @@ -144,6 +144,7 @@ fn configure_client(secure_conn: bool, alpns: Vec) -> Result>(); crypto.alpn_protocols = alpn_protocols; + crypto.key_log = Arc::new(rustls::KeyLogFile::new()); Ok(ClientConfig::new(Arc::new(crypto))) } @@ -234,6 +235,7 @@ fn configure_server( .map(|x| x.as_bytes().to_vec()) .collect::>(); crypto.alpn_protocols = alpn_protocols; + crypto.key_log = Arc::new(rustls::KeyLogFile::new()); let mut server_config = ServerConfig::with_crypto(Arc::new(crypto)); Arc::get_mut(&mut server_config.transport) From 8b64c734e7e45e6a290b4e2f585c843e0a85c109 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 30 Apr 2024 15:57:09 +0530 Subject: [PATCH 05/12] net/quic: Use separate property for address and port While at it, do not duplicate call to settings lock in property getter and setter for every property. Part-of: --- net/quic/src/quicsink/imp.rs | 126 +++++++++++++++++------------------ net/quic/src/quicsrc/imp.rs | 94 +++++++++++--------------- net/quic/src/utils.rs | 16 +++-- net/quic/tests/quic.rs | 11 +-- 4 files changed, 116 insertions(+), 131 deletions(-) diff --git a/net/quic/src/quicsink/imp.rs b/net/quic/src/quicsink/imp.rs index 95eb2cf0..6024ec36 100644 --- a/net/quic/src/quicsink/imp.rs +++ b/net/quic/src/quicsink/imp.rs @@ -16,12 +16,14 @@ use gst::{glib, prelude::*, subclass::prelude::*}; use gst_base::subclass::prelude::*; use once_cell::sync::Lazy; use quinn::{Connection, SendStream}; -use std::net::SocketAddr; use std::sync::Mutex; static DEFAULT_SERVER_NAME: &str = "localhost"; -static DEFAULT_SERVER_ADDR: &str = "127.0.0.1:5000"; -static DEFAULT_CLIENT_ADDR: &str = "127.0.0.1:5001"; +static DEFAULT_SERVER_ADDR: &str = "127.0.0.1"; +static DEFAULT_SERVER_PORT: u16 = 5000; +static DEFAULT_CLIENT_ADDR: &str = "127.0.0.1"; +static DEFAULT_CLIENT_PORT: u16 = 5001; + /* * For QUIC transport parameters * @@ -52,8 +54,10 @@ enum State { #[derive(Clone, Debug)] struct Settings { - client_address: SocketAddr, - server_address: SocketAddr, + client_address: String, + client_port: u16, + server_address: String, + server_port: u16, server_name: String, alpns: Vec, timeout: u32, @@ -64,8 +68,10 @@ struct Settings { impl Default for Settings { fn default() -> Self { Settings { - client_address: DEFAULT_CLIENT_ADDR.parse::().unwrap(), - server_address: DEFAULT_SERVER_ADDR.parse::().unwrap(), + client_address: DEFAULT_CLIENT_ADDR.to_string(), + client_port: DEFAULT_CLIENT_PORT, + server_address: DEFAULT_SERVER_ADDR.to_string(), + server_port: DEFAULT_SERVER_PORT, server_name: DEFAULT_SERVER_NAME.to_string(), alpns: vec![DEFAULT_ALPN.to_string()], timeout: DEFAULT_TIMEOUT, @@ -137,11 +143,25 @@ impl ObjectImpl for QuicSink { .build(), glib::ParamSpecString::builder("server-address") .nick("QUIC server address") - .blurb("Address of the QUIC server to connect to e.g. 127.0.0.1:5000") + .blurb("Address of the QUIC server to connect to e.g. 127.0.0.1") + .build(), + glib::ParamSpecUInt::builder("server-port") + .nick("QUIC server port") + .blurb("Port of the QUIC server to connect to e.g. 5000") + .maximum(65535) + .default_value(DEFAULT_SERVER_PORT as u32) + .readwrite() .build(), glib::ParamSpecString::builder("client-address") .nick("QUIC client address") - .blurb("Address to be used by this QUIC client e.g. 127.0.0.1:5001") + .blurb("Address to be used by this QUIC client e.g. 127.0.0.1") + .build(), + glib::ParamSpecUInt::builder("client-port") + .nick("QUIC client port") + .blurb("Port to be used by this QUIC client e.g. 5001") + .maximum(65535) + .default_value(DEFAULT_CLIENT_PORT as u32) + .readwrite() .build(), gst::ParamSpecArray::builder("alpn-protocols") .nick("QUIC ALPN values") @@ -172,43 +192,25 @@ impl ObjectImpl for QuicSink { } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + let mut settings = self.settings.lock().unwrap(); + match pspec.name() { "server-name" => { - let mut settings = self.settings.lock().unwrap(); settings.server_name = value.get::().expect("type checked upstream"); } "server-address" => { - let addr = value.get::().expect("type checked upstream"); - let addr = make_socket_addr(&addr); - match addr { - Ok(server_address) => { - let mut settings = self.settings.lock().unwrap(); - settings.server_address = server_address; - } - Err(e) => gst::element_imp_error!( - self, - gst::ResourceError::Failed, - ["Invalid server address: {}", e] - ), - } + settings.server_address = value.get::().expect("type checked upstream"); + } + "server-port" => { + settings.server_port = value.get::().expect("type checked upstream") as u16; } "client-address" => { - let addr = value.get::().expect("type checked upstream"); - let addr = make_socket_addr(&addr); - match addr { - Ok(client_address) => { - let mut settings = self.settings.lock().unwrap(); - settings.client_address = client_address; - } - Err(e) => gst::element_imp_error!( - self, - gst::ResourceError::Failed, - ["Invalid client address: {}", e] - ), - } + settings.client_address = value.get::().expect("type checked upstream"); + } + "client-port" => { + settings.client_port = value.get::().expect("type checked upstream") as u16; } "alpn-protocols" => { - let mut settings = self.settings.lock().unwrap(); settings.alpns = value .get::() .expect("type checked upstream") @@ -222,15 +224,12 @@ impl ObjectImpl for QuicSink { .collect::>(); } "timeout" => { - let mut settings = self.settings.lock().unwrap(); settings.timeout = value.get().expect("type checked upstream"); } "secure-connection" => { - let mut settings = self.settings.lock().unwrap(); settings.secure_conn = value.get().expect("type checked upstream"); } "use-datagram" => { - let mut settings = self.settings.lock().unwrap(); settings.use_datagram = value.get().expect("type checked upstream"); } _ => unimplemented!(), @@ -238,36 +237,27 @@ impl ObjectImpl for QuicSink { } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + let settings = self.settings.lock().unwrap(); + match pspec.name() { - "server-name" => { - let settings = self.settings.lock().unwrap(); - settings.server_name.to_value() + "server-name" => settings.server_name.to_value(), + "server-address" => settings.server_address.to_string().to_value(), + "server-port" => { + let port = settings.server_port as u32; + port.to_value() } - "server-address" => { - let settings = self.settings.lock().unwrap(); - settings.server_address.to_string().to_value() - } - "client-address" => { - let settings = self.settings.lock().unwrap(); - settings.client_address.to_string().to_value() + "client-address" => settings.client_address.to_string().to_value(), + "client-port" => { + let port = settings.client_port as u32; + port.to_value() } "alpn-protocols" => { - let settings = self.settings.lock().unwrap(); let alpns = settings.alpns.iter().map(|v| v.as_str()); gst::Array::new(alpns).to_value() } - "timeout" => { - let settings = self.settings.lock().unwrap(); - settings.timeout.to_value() - } - "secure-connection" => { - let settings = self.settings.lock().unwrap(); - settings.secure_conn.to_value() - } - "use-datagram" => { - let settings = self.settings.lock().unwrap(); - settings.use_datagram.to_value() - } + "timeout" => settings.timeout.to_value(), + "secure-connection" => settings.secure_conn.to_value(), + "use-datagram" => settings.use_datagram.to_value(), _ => unimplemented!(), } } @@ -465,8 +455,14 @@ impl QuicSink { { let settings = self.settings.lock().unwrap(); - client_addr = settings.client_address; - server_addr = settings.server_address; + + client_addr = make_socket_addr( + format!("{}:{}", settings.client_address, settings.client_port).as_str(), + )?; + server_addr = make_socket_addr( + format!("{}:{}", settings.server_address, settings.server_port).as_str(), + )?; + server_name = settings.server_name.clone(); alpns = settings.alpns.clone(); use_datagram = settings.use_datagram; diff --git a/net/quic/src/quicsrc/imp.rs b/net/quic/src/quicsrc/imp.rs index 8fc48803..c45a21ea 100644 --- a/net/quic/src/quicsrc/imp.rs +++ b/net/quic/src/quicsrc/imp.rs @@ -19,12 +19,13 @@ use gst_base::subclass::base_src::CreateSuccess; use gst_base::subclass::prelude::*; use once_cell::sync::Lazy; use quinn::{Connection, ConnectionError, RecvStream}; -use std::net::SocketAddr; use std::path::PathBuf; use std::sync::Mutex; static DEFAULT_SERVER_NAME: &str = "localhost"; -static DEFAULT_SERVER_ADDR: &str = "127.0.0.1:5000"; +static DEFAULT_SERVER_ADDR: &str = "127.0.0.1"; +static DEFAULT_SERVER_PORT: u16 = 5000; + /* * For QUIC transport parameters * @@ -60,7 +61,8 @@ enum State { #[derive(Clone, Debug)] struct Settings { - server_address: SocketAddr, + server_address: String, + server_port: u16, server_name: String, alpns: Vec, timeout: u32, @@ -74,7 +76,8 @@ struct Settings { impl Default for Settings { fn default() -> Self { Settings { - server_address: DEFAULT_SERVER_ADDR.parse::().unwrap(), + server_address: DEFAULT_SERVER_ADDR.to_string(), + server_port: DEFAULT_SERVER_PORT, server_name: DEFAULT_SERVER_NAME.to_string(), alpns: vec![DEFAULT_ALPN.to_string()], timeout: DEFAULT_TIMEOUT, @@ -176,7 +179,14 @@ impl ObjectImpl for QuicSrc { .build(), glib::ParamSpecString::builder("server-address") .nick("QUIC server address") - .blurb("Address of the QUIC server to connect to e.g. 127.0.0.1:5000") + .blurb("Address of the QUIC server e.g. 127.0.0.1") + .build(), + glib::ParamSpecUInt::builder("server-port") + .nick("QUIC server port") + .blurb("Port of the QUIC server e.g. 5000") + .maximum(65535) + .default_value(DEFAULT_SERVER_PORT as u32) + .readwrite() .build(), gst::ParamSpecArray::builder("alpn-protocols") .nick("QUIC ALPN values") @@ -219,28 +229,19 @@ impl ObjectImpl for QuicSrc { } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + let mut settings = self.settings.lock().unwrap(); + match pspec.name() { "server-name" => { - let mut settings = self.settings.lock().unwrap(); settings.server_name = value.get::().expect("type checked upstream"); } "server-address" => { - let addr = value.get::().expect("type checked upstream"); - let addr = make_socket_addr(&addr); - match addr { - Ok(server_address) => { - let mut settings = self.settings.lock().unwrap(); - settings.server_address = server_address; - } - Err(e) => gst::element_imp_error!( - self, - gst::ResourceError::Failed, - ["Invalid server address: {}", e] - ), - } + settings.server_address = value.get::().expect("type checked upstream"); + } + "server-port" => { + settings.server_port = value.get::().expect("type checked upstream") as u16; } "alpn-protocols" => { - let mut settings = self.settings.lock().unwrap(); settings.alpns = value .get::() .expect("type checked upstream") @@ -254,7 +255,6 @@ impl ObjectImpl for QuicSrc { .collect::>() } "caps" => { - let mut settings = self.settings.lock().unwrap(); settings.caps = value .get::>() .expect("type checked upstream") @@ -264,24 +264,19 @@ impl ObjectImpl for QuicSrc { srcpad.mark_reconfigure(); } "timeout" => { - let mut settings = self.settings.lock().unwrap(); settings.timeout = value.get().expect("type checked upstream"); } "secure-connection" => { - let mut settings = self.settings.lock().unwrap(); settings.secure_conn = value.get().expect("type checked upstream"); } "certificate-path" => { let value: String = value.get().unwrap(); - let mut settings = self.settings.lock().unwrap(); settings.certificate_path = Some(value.into()); } "use-datagram" => { - let mut settings = self.settings.lock().unwrap(); settings.use_datagram = value.get().expect("type checked upstream"); } "private-key-type" => { - let mut settings = self.settings.lock().unwrap(); settings.private_key_type = value .get::() .expect("type checked upstream"); @@ -291,45 +286,28 @@ impl ObjectImpl for QuicSrc { } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + let settings = self.settings.lock().unwrap(); + match pspec.name() { - "server-name" => { - let settings = self.settings.lock().unwrap(); - settings.server_name.to_value() - } - "server-address" => { - let settings = self.settings.lock().unwrap(); - settings.server_address.to_string().to_value() + "server-name" => settings.server_name.to_value(), + "server-address" => settings.server_address.to_string().to_value(), + "server-port" => { + let port = settings.server_port as u32; + port.to_value() } "alpn-protocols" => { - let settings = self.settings.lock().unwrap(); let alpns = settings.alpns.iter().map(|v| v.as_str()); gst::Array::new(alpns).to_value() } - "caps" => { - let settings = self.settings.lock().unwrap(); - settings.caps.to_value() - } - "timeout" => { - let settings = self.settings.lock().unwrap(); - settings.timeout.to_value() - } - "secure-connection" => { - let settings = self.settings.lock().unwrap(); - settings.secure_conn.to_value() - } + "caps" => settings.caps.to_value(), + "timeout" => settings.timeout.to_value(), + "secure-connection" => settings.secure_conn.to_value(), "certificate-path" => { - let settings = self.settings.lock().unwrap(); let certpath = settings.certificate_path.as_ref(); certpath.and_then(|file| file.to_str()).to_value() } - "use-datagram" => { - let settings = self.settings.lock().unwrap(); - settings.use_datagram.to_value() - } - "private-key-type" => { - let settings = self.settings.lock().unwrap(); - settings.private_key_type.to_value() - } + "use-datagram" => settings.use_datagram.to_value(), + "private-key-type" => settings.private_key_type.to_value(), _ => unimplemented!(), } } @@ -563,7 +541,11 @@ impl QuicSrc { { let settings = self.settings.lock().unwrap(); - server_addr = settings.server_address; + + server_addr = make_socket_addr( + format!("{}:{}", settings.server_address, settings.server_port).as_str(), + )?; + server_name = settings.server_name.clone(); alpns = settings.alpns.clone(); use_datagram = settings.use_datagram; diff --git a/net/quic/src/utils.rs b/net/quic/src/utils.rs index 1d2ab185..04de1187 100644 --- a/net/quic/src/utils.rs +++ b/net/quic/src/utils.rs @@ -16,7 +16,7 @@ use quinn::{ClientConfig, Endpoint, ServerConfig}; use std::error::Error; use std::fs::File; use std::io::BufReader; -use std::net::{AddrParseError, SocketAddr}; +use std::net::SocketAddr; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -102,13 +102,19 @@ where res } +pub fn make_socket_addr(addr: &str) -> Result { + match addr.parse::() { + Ok(address) => Ok(address), + Err(e) => Err(WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Invalid address: {}", e] + ))), + } +} + /* * Following functions are taken from Quinn documentation/repository */ -pub fn make_socket_addr(addr: &str) -> Result { - addr.parse::() -} - struct SkipServerVerification; impl SkipServerVerification { diff --git a/net/quic/tests/quic.rs b/net/quic/tests/quic.rs index 11be17ac..80c7cc35 100644 --- a/net/quic/tests/quic.rs +++ b/net/quic/tests/quic.rs @@ -35,7 +35,8 @@ fn test_send_receive_without_datagram() { let content = "Hello, world!\n".as_bytes(); thread::spawn(move || { - let mut h1 = gst_check::Harness::new("quicsink"); + let mut h1 = gst_check::Harness::new_empty(); + h1.add_parse("quicsink secure-connection=false"); h1.set_src_caps(gst::Caps::builder("text/plain").build()); @@ -50,7 +51,8 @@ fn test_send_receive_without_datagram() { drop(h1); }); - let mut h2 = gst_check::Harness::new("quicsrc"); + let mut h2 = gst_check::Harness::new_empty(); + h2.add_parse("quicsrc secure-connection=false"); h2.play(); @@ -77,8 +79,8 @@ fn test_send_receive_with_datagram() { // in the other test. We get a address already in use error otherwise. thread::spawn(move || { let mut h1 = gst_check::Harness::new_empty(); + h1.add_parse("quicsrc use-datagram=true server-address=127.0.0.1 server-port=6000 secure-connection=false"); - h1.add_parse(format!("quicsrc use-datagram=true server-address=127.0.0.1:6000").as_str()); h1.play(); let buf = h1.pull_until_eos().unwrap().unwrap(); @@ -94,8 +96,7 @@ fn test_send_receive_with_datagram() { }); let mut h2 = gst_check::Harness::new_empty(); - - h2.add_parse(format!("quicsink use-datagram=true client-address=127.0.0.1:6001 server-address=127.0.0.1:6000").as_str()); + h2.add_parse("quicsink use-datagram=true client-address=127.0.0.1 client-port=6001 server-address=127.0.0.1 server-port=6000 secure-connection=false"); h2.set_src_caps(gst::Caps::builder("text/plain").build()); From 22c6a98914372cf478672c57f4c96fc082612511 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 30 Apr 2024 16:23:53 +0530 Subject: [PATCH 06/12] net/quic: Rename to quinnquicsink/src There might be other QUIC elements in the future based on other libraries. To prevent namespace collision, namespace the elements with `quinn` prefix. Part-of: --- net/quic/src/lib.rs | 8 +++--- .../src/{quicsink => quinnquicsink}/imp.rs | 28 +++++++++++-------- .../src/{quicsink => quinnquicsink}/mod.rs | 6 ++-- net/quic/src/{quicsrc => quinnquicsrc}/imp.rs | 26 ++++++++--------- net/quic/src/{quicsrc => quinnquicsrc}/mod.rs | 6 ++-- net/quic/src/utils.rs | 2 +- net/quic/tests/{quic.rs => quinnquic.rs} | 8 +++--- 7 files changed, 44 insertions(+), 40 deletions(-) rename net/quic/src/{quicsink => quinnquicsink}/imp.rs (97%) rename net/quic/src/{quicsink => quinnquicsink}/mod.rs (75%) rename net/quic/src/{quicsrc => quinnquicsrc}/imp.rs (97%) rename net/quic/src/{quicsrc => quinnquicsrc}/mod.rs (83%) rename net/quic/tests/{quic.rs => quinnquic.rs} (86%) diff --git a/net/quic/src/lib.rs b/net/quic/src/lib.rs index ffbd29a7..f54ce6b5 100644 --- a/net/quic/src/lib.rs +++ b/net/quic/src/lib.rs @@ -14,13 +14,13 @@ * Since: plugins-rs-0.11.0 */ use gst::glib; -mod quicsink; -mod quicsrc; +mod quinnquicsink; +mod quinnquicsrc; mod utils; fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - quicsink::register(plugin)?; - quicsrc::register(plugin)?; + quinnquicsink::register(plugin)?; + quinnquicsrc::register(plugin)?; Ok(()) } diff --git a/net/quic/src/quicsink/imp.rs b/net/quic/src/quinnquicsink/imp.rs similarity index 97% rename from net/quic/src/quicsink/imp.rs rename to net/quic/src/quinnquicsink/imp.rs index 6024ec36..737dbd22 100644 --- a/net/quic/src/quicsink/imp.rs +++ b/net/quic/src/quinnquicsink/imp.rs @@ -37,7 +37,11 @@ const DEFAULT_TIMEOUT: u32 = 15; const DEFAULT_SECURE_CONNECTION: bool = true; static CAT: Lazy = Lazy::new(|| { - gst::DebugCategory::new("quicsink", gst::DebugColorFlags::empty(), Some("QUIC Sink")) + gst::DebugCategory::new( + "quinnquicsink", + gst::DebugColorFlags::empty(), + Some("Quinn QUIC Sink"), + ) }); struct Started { @@ -81,13 +85,13 @@ impl Default for Settings { } } -pub struct QuicSink { +pub struct QuinnQuicSink { settings: Mutex, state: Mutex, canceller: Mutex>, } -impl Default for QuicSink { +impl Default for QuinnQuicSink { fn default() -> Self { Self { settings: Mutex::new(Settings::default()), @@ -97,13 +101,13 @@ impl Default for QuicSink { } } -impl GstObjectImpl for QuicSink {} +impl GstObjectImpl for QuinnQuicSink {} -impl ElementImpl for QuicSink { +impl ElementImpl for QuinnQuicSink { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { gst::subclass::ElementMetadata::new( - "QUIC Sink", + "Quinn QUIC Sink", "Source/Network/QUIC", "Send data over the network via QUIC", "Sanchayan Maity ", @@ -129,7 +133,7 @@ impl ElementImpl for QuicSink { } } -impl ObjectImpl for QuicSink { +impl ObjectImpl for QuinnQuicSink { fn constructed(&self) { self.parent_constructed(); } @@ -264,13 +268,13 @@ impl ObjectImpl for QuicSink { } #[glib::object_subclass] -impl ObjectSubclass for QuicSink { - const NAME: &'static str = "GstQUICSink"; - type Type = super::QuicSink; +impl ObjectSubclass for QuinnQuicSink { + const NAME: &'static str = "GstQuinnQUICSink"; + type Type = super::QuinnQuicSink; type ParentType = gst_base::BaseSink; } -impl BaseSinkImpl for QuicSink { +impl BaseSinkImpl for QuinnQuicSink { fn start(&self) -> Result<(), gst::ErrorMessage> { let settings = self.settings.lock().unwrap(); let timeout = settings.timeout; @@ -392,7 +396,7 @@ impl BaseSinkImpl for QuicSink { } } -impl QuicSink { +impl QuinnQuicSink { fn send_buffer(&self, src: &[u8]) -> Result<(), Option> { let settings = self.settings.lock().unwrap(); let timeout = settings.timeout; diff --git a/net/quic/src/quicsink/mod.rs b/net/quic/src/quinnquicsink/mod.rs similarity index 75% rename from net/quic/src/quicsink/mod.rs rename to net/quic/src/quinnquicsink/mod.rs index 728932ae..198ddd94 100644 --- a/net/quic/src/quicsink/mod.rs +++ b/net/quic/src/quinnquicsink/mod.rs @@ -13,14 +13,14 @@ use gst::prelude::*; pub mod imp; glib::wrapper! { - pub struct QuicSink(ObjectSubclass) @extends gst_base::BaseSink, gst::Element, gst::Object; + pub struct QuinnQuicSink(ObjectSubclass) @extends gst_base::BaseSink, gst::Element, gst::Object; } pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { gst::Element::register( Some(plugin), - "quicsink", + "quinnquicsink", gst::Rank::MARGINAL, - QuicSink::static_type(), + QuinnQuicSink::static_type(), ) } diff --git a/net/quic/src/quicsrc/imp.rs b/net/quic/src/quinnquicsrc/imp.rs similarity index 97% rename from net/quic/src/quicsrc/imp.rs rename to net/quic/src/quinnquicsrc/imp.rs index c45a21ea..9a17cad2 100644 --- a/net/quic/src/quicsrc/imp.rs +++ b/net/quic/src/quinnquicsrc/imp.rs @@ -41,9 +41,9 @@ const DEFAULT_SECURE_CONNECTION: bool = true; static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new( - "quicsrc", + "quinnquicsrc", gst::DebugColorFlags::empty(), - Some("QUIC Source"), + Some("Quinn QUIC Source"), ) }); @@ -90,13 +90,13 @@ impl Default for Settings { } } -pub struct QuicSrc { +pub struct QuinnQuicSrc { settings: Mutex, state: Mutex, canceller: Mutex>, } -impl Default for QuicSrc { +impl Default for QuinnQuicSrc { fn default() -> Self { Self { settings: Mutex::new(Settings::default()), @@ -106,15 +106,15 @@ impl Default for QuicSrc { } } -impl GstObjectImpl for QuicSrc {} +impl GstObjectImpl for QuinnQuicSrc {} -impl ElementImpl for QuicSrc { +impl ElementImpl for QuinnQuicSrc { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { #[cfg(feature = "doc")] QuicPrivateKeyType::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty()); gst::subclass::ElementMetadata::new( - "QUIC Source", + "Quinn QUIC Source", "Source/Network/QUIC", "Receive data over the network via QUIC", "Sanchayan Maity ", @@ -164,7 +164,7 @@ impl ElementImpl for QuicSrc { } } -impl ObjectImpl for QuicSrc { +impl ObjectImpl for QuinnQuicSrc { fn constructed(&self) { self.parent_constructed(); self.obj().set_format(gst::Format::Bytes); @@ -314,13 +314,13 @@ impl ObjectImpl for QuicSrc { } #[glib::object_subclass] -impl ObjectSubclass for QuicSrc { - const NAME: &'static str = "GstQUICSrc"; - type Type = super::QuicSrc; +impl ObjectSubclass for QuinnQuicSrc { + const NAME: &'static str = "GstQuinnQUICSrc"; + type Type = super::QuinnQuicSrc; type ParentType = gst_base::BaseSrc; } -impl BaseSrcImpl for QuicSrc { +impl BaseSrcImpl for QuinnQuicSrc { fn is_seekable(&self) -> bool { false } @@ -458,7 +458,7 @@ impl BaseSrcImpl for QuicSrc { } } -impl QuicSrc { +impl QuinnQuicSrc { fn get(&self, _offset: u64, length: u64) -> Result> { let settings = self.settings.lock().unwrap(); let timeout = settings.timeout; diff --git a/net/quic/src/quicsrc/mod.rs b/net/quic/src/quinnquicsrc/mod.rs similarity index 83% rename from net/quic/src/quicsrc/mod.rs rename to net/quic/src/quinnquicsrc/mod.rs index 2eca4854..7af4afa9 100644 --- a/net/quic/src/quicsrc/mod.rs +++ b/net/quic/src/quinnquicsrc/mod.rs @@ -23,14 +23,14 @@ pub enum QuicPrivateKeyType { } glib::wrapper! { - pub struct QuicSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; + pub struct QuinnQuicSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; } pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { gst::Element::register( Some(plugin), - "quicsrc", + "quinnquicsrc", gst::Rank::MARGINAL, - QuicSrc::static_type(), + QuinnQuicSrc::static_type(), ) } diff --git a/net/quic/src/utils.rs b/net/quic/src/utils.rs index 04de1187..4c5f5152 100644 --- a/net/quic/src/utils.rs +++ b/net/quic/src/utils.rs @@ -7,7 +7,7 @@ // // SPDX-License-Identifier: MPL-2.0 -use crate::quicsrc::QuicPrivateKeyType; +use crate::quinnquicsrc::QuicPrivateKeyType; use futures::future; use futures::prelude::*; use gst::ErrorMessage; diff --git a/net/quic/tests/quic.rs b/net/quic/tests/quinnquic.rs similarity index 86% rename from net/quic/tests/quic.rs rename to net/quic/tests/quinnquic.rs index 80c7cc35..5a6809ec 100644 --- a/net/quic/tests/quic.rs +++ b/net/quic/tests/quinnquic.rs @@ -36,7 +36,7 @@ fn test_send_receive_without_datagram() { thread::spawn(move || { let mut h1 = gst_check::Harness::new_empty(); - h1.add_parse("quicsink secure-connection=false"); + h1.add_parse("quinnquicsink secure-connection=false"); h1.set_src_caps(gst::Caps::builder("text/plain").build()); @@ -52,7 +52,7 @@ fn test_send_receive_without_datagram() { }); let mut h2 = gst_check::Harness::new_empty(); - h2.add_parse("quicsrc secure-connection=false"); + h2.add_parse("quinnquicsrc secure-connection=false"); h2.play(); @@ -79,7 +79,7 @@ fn test_send_receive_with_datagram() { // in the other test. We get a address already in use error otherwise. thread::spawn(move || { let mut h1 = gst_check::Harness::new_empty(); - h1.add_parse("quicsrc use-datagram=true server-address=127.0.0.1 server-port=6000 secure-connection=false"); + h1.add_parse("quinnquicsrc use-datagram=true server-address=127.0.0.1 server-port=6000 secure-connection=false"); h1.play(); @@ -96,7 +96,7 @@ fn test_send_receive_with_datagram() { }); let mut h2 = gst_check::Harness::new_empty(); - h2.add_parse("quicsink use-datagram=true client-address=127.0.0.1 client-port=6001 server-address=127.0.0.1 server-port=6000 secure-connection=false"); + h2.add_parse("quinnquicsink use-datagram=true client-address=127.0.0.1 client-port=6001 server-address=127.0.0.1 server-port=6000 secure-connection=false"); h2.set_src_caps(gst::Caps::builder("text/plain").build()); From a306b1ce94b344d3d868ed25e3b588412c36ce0a Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 30 Apr 2024 16:42:41 +0530 Subject: [PATCH 07/12] net/quic: Use a custom ALPN string `h3` does not make sense as the default ALPN, as there likely isn't going to be a HTTP/3 application layer, especially as our transport is unidirectional for now. Use a custom string `gst-quinn` for now. Part-of: --- net/quic/src/quinnquicsink/imp.rs | 2 +- net/quic/src/quinnquicsrc/imp.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/quic/src/quinnquicsink/imp.rs b/net/quic/src/quinnquicsink/imp.rs index 737dbd22..b324ec69 100644 --- a/net/quic/src/quinnquicsink/imp.rs +++ b/net/quic/src/quinnquicsink/imp.rs @@ -32,7 +32,7 @@ static DEFAULT_CLIENT_PORT: u16 = 5001; * Other well-known values are listed in the at IANA registry at * . */ -const DEFAULT_ALPN: &str = "h3"; +const DEFAULT_ALPN: &str = "gst-quinn"; const DEFAULT_TIMEOUT: u32 = 15; const DEFAULT_SECURE_CONNECTION: bool = true; diff --git a/net/quic/src/quinnquicsrc/imp.rs b/net/quic/src/quinnquicsrc/imp.rs index 9a17cad2..73e96489 100644 --- a/net/quic/src/quinnquicsrc/imp.rs +++ b/net/quic/src/quinnquicsrc/imp.rs @@ -34,7 +34,7 @@ static DEFAULT_SERVER_PORT: u16 = 5000; * Other well-known values are listed in the at IANA registry at * . */ -const DEFAULT_ALPN: &str = "h3"; +const DEFAULT_ALPN: &str = "gst-quinn"; const DEFAULT_TIMEOUT: u32 = 15; const DEFAULT_PRIVATE_KEY_TYPE: QuicPrivateKeyType = QuicPrivateKeyType::Pkcs8; const DEFAULT_SECURE_CONNECTION: bool = true; From 97d8a79d3680b17994e34d4a995ebc17d56fa61a Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 1 May 2024 11:41:08 +0530 Subject: [PATCH 08/12] net/quic: Drop private key type property Use read_all helper from rustls_pemfile and drop the requirement for the user having to specify the private key type. Part-of: --- net/quic/src/quinnquicsrc/imp.rs | 39 ++++++-------------------------- net/quic/src/quinnquicsrc/mod.rs | 10 -------- net/quic/src/utils.rs | 35 ++++++++++------------------ 3 files changed, 19 insertions(+), 65 deletions(-) diff --git a/net/quic/src/quinnquicsrc/imp.rs b/net/quic/src/quinnquicsrc/imp.rs index 73e96489..6546fb3b 100644 --- a/net/quic/src/quinnquicsrc/imp.rs +++ b/net/quic/src/quinnquicsrc/imp.rs @@ -7,7 +7,6 @@ // // SPDX-License-Identifier: MPL-2.0 -use super::QuicPrivateKeyType; use crate::utils::{ make_socket_addr, server_endpoint, wait, WaitError, CONNECTION_CLOSE_CODE, CONNECTION_CLOSE_MSG, }; @@ -36,7 +35,6 @@ static DEFAULT_SERVER_PORT: u16 = 5000; */ const DEFAULT_ALPN: &str = "gst-quinn"; const DEFAULT_TIMEOUT: u32 = 15; -const DEFAULT_PRIVATE_KEY_TYPE: QuicPrivateKeyType = QuicPrivateKeyType::Pkcs8; const DEFAULT_SECURE_CONNECTION: bool = true; static CAT: Lazy = Lazy::new(|| { @@ -70,7 +68,6 @@ struct Settings { caps: gst::Caps, use_datagram: bool, certificate_path: Option, - private_key_type: QuicPrivateKeyType, } impl Default for Settings { @@ -85,7 +82,6 @@ impl Default for Settings { caps: gst::Caps::new_any(), use_datagram: false, certificate_path: None, - private_key_type: DEFAULT_PRIVATE_KEY_TYPE, } } } @@ -111,8 +107,6 @@ impl GstObjectImpl for QuinnQuicSrc {} impl ElementImpl for QuinnQuicSrc { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { - #[cfg(feature = "doc")] - QuicPrivateKeyType::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty()); gst::subclass::ElementMetadata::new( "Quinn QUIC Source", "Source/Network/QUIC", @@ -218,10 +212,6 @@ impl ObjectImpl for QuinnQuicSrc { .blurb("Use datagram for lower latency, unreliable messaging") .default_value(false) .build(), - glib::ParamSpecEnum::builder_with_default::("private-key-type", DEFAULT_PRIVATE_KEY_TYPE) - .nick("Whether PKCS8 or RSA key type is considered for private key") - .blurb("Read given private key as PKCS8 or RSA") - .build(), ] }); @@ -276,11 +266,6 @@ impl ObjectImpl for QuinnQuicSrc { "use-datagram" => { settings.use_datagram = value.get().expect("type checked upstream"); } - "private-key-type" => { - settings.private_key_type = value - .get::() - .expect("type checked upstream"); - } _ => unimplemented!(), } } @@ -307,7 +292,6 @@ impl ObjectImpl for QuinnQuicSrc { certpath.and_then(|file| file.to_str()).to_value() } "use-datagram" => settings.use_datagram.to_value(), - "private-key-type" => settings.private_key_type.to_value(), _ => unimplemented!(), } } @@ -537,7 +521,6 @@ impl QuinnQuicSrc { let use_datagram; let secure_conn; let cert_path; - let private_key_type; { let settings = self.settings.lock().unwrap(); @@ -551,23 +534,15 @@ impl QuinnQuicSrc { use_datagram = settings.use_datagram; secure_conn = settings.secure_conn; cert_path = settings.certificate_path.clone(); - private_key_type = settings.private_key_type; } - let endpoint = server_endpoint( - server_addr, - &server_name, - secure_conn, - alpns, - cert_path, - private_key_type, - ) - .map_err(|err| { - WaitError::FutureError(gst::error_msg!( - gst::ResourceError::Failed, - ["Failed to configure endpoint: {}", err] - )) - })?; + let endpoint = server_endpoint(server_addr, &server_name, secure_conn, alpns, cert_path) + .map_err(|err| { + WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Failed to configure endpoint: {}", err] + )) + })?; let incoming_conn = endpoint.accept().await.unwrap(); diff --git a/net/quic/src/quinnquicsrc/mod.rs b/net/quic/src/quinnquicsrc/mod.rs index 7af4afa9..0acf7876 100644 --- a/net/quic/src/quinnquicsrc/mod.rs +++ b/net/quic/src/quinnquicsrc/mod.rs @@ -12,16 +12,6 @@ use gst::prelude::*; mod imp; -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)] -#[repr(u32)] -#[enum_type(name = "GstQuicPrivateKeyType")] -pub enum QuicPrivateKeyType { - #[enum_value(name = "PKCS8: PKCS #8 Private Key.", nick = "pkcs8")] - Pkcs8, - #[enum_value(name = "RSA: RSA Private Key.", nick = "rsa")] - Rsa, -} - glib::wrapper! { pub struct QuinnQuicSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; } diff --git a/net/quic/src/utils.rs b/net/quic/src/utils.rs index 4c5f5152..8ced1636 100644 --- a/net/quic/src/utils.rs +++ b/net/quic/src/utils.rs @@ -7,7 +7,6 @@ // // SPDX-License-Identifier: MPL-2.0 -use crate::quinnquicsrc::QuicPrivateKeyType; use futures::future; use futures::prelude::*; use gst::ErrorMessage; @@ -158,7 +157,6 @@ fn configure_client(secure_conn: bool, alpns: Vec) -> Result, - private_key_type: QuicPrivateKeyType, ) -> Result<(Vec, rustls::PrivateKey), Box> { /* * NOTE: @@ -192,19 +190,18 @@ fn read_certs_from_file( let key: rustls::PrivateKey = { let key_file = File::open(key_file.as_path())?; let mut key_file_rdr = BufReader::new(key_file); - let mut key_vec; - // If the file starts with "BEGIN RSA PRIVATE KEY" - if let QuicPrivateKeyType::Rsa = private_key_type { - key_vec = rustls_pemfile::rsa_private_keys(&mut key_file_rdr)?; - } else { - // If the file starts with "BEGIN PRIVATE KEY" - key_vec = rustls_pemfile::pkcs8_private_keys(&mut key_file_rdr)?; + let keys_iter = rustls_pemfile::read_all(&mut key_file_rdr)?; + let key_item = keys_iter + .into_iter() + .next() + .ok_or("Certificate should have at least one private key")?; + + match key_item { + rustls_pemfile::Item::RSAKey(key) => rustls::PrivateKey(key), + rustls_pemfile::Item::PKCS8Key(key) => rustls::PrivateKey(key), + _ => unimplemented!(), } - - assert_eq!(key_vec.len(), 1); - - rustls::PrivateKey(key_vec.remove(0)) }; Ok((certs, key)) @@ -215,10 +212,9 @@ fn configure_server( secure_conn: bool, certificate_path: Option, alpns: Vec, - private_key_type: QuicPrivateKeyType, ) -> Result<(ServerConfig, Vec), Box> { let (cert, key) = if secure_conn { - read_certs_from_file(certificate_path, private_key_type).unwrap() + read_certs_from_file(certificate_path).unwrap() } else { let cert = rcgen::generate_simple_self_signed(vec![server_name.into()]).unwrap(); let cert_der = cert.serialize_der().unwrap(); @@ -258,15 +254,8 @@ pub fn server_endpoint( secure_conn: bool, alpns: Vec, certificate_path: Option, - private_key_type: QuicPrivateKeyType, ) -> Result> { - let (server_config, _) = configure_server( - server_name, - secure_conn, - certificate_path, - alpns, - private_key_type, - )?; + let (server_config, _) = configure_server(server_name, secure_conn, certificate_path, alpns)?; let endpoint = Endpoint::server(server_config, server_addr)?; Ok(endpoint) From 18cf5292b75df3b42bde5d05faadf6cab7e64e40 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 1 May 2024 14:24:04 +0530 Subject: [PATCH 09/12] net/quic: Fix inconsistencies around secure connection handling This set of changes implements the below fixes: - Allow certificates to be specified for client/quicsink - Secure connection being true on server/quicsrc and false on client/quicsink still resulted in a successful connection instead of server rejecting the connection - Using secure connection with ALPN was not working Part-of: --- net/quic/src/quinnquicsink/imp.rs | 54 +++++++++++++++++++--- net/quic/src/utils.rs | 77 +++++++++++++++++++++---------- 2 files changed, 100 insertions(+), 31 deletions(-) diff --git a/net/quic/src/quinnquicsink/imp.rs b/net/quic/src/quinnquicsink/imp.rs index b324ec69..c3770864 100644 --- a/net/quic/src/quinnquicsink/imp.rs +++ b/net/quic/src/quinnquicsink/imp.rs @@ -16,6 +16,7 @@ use gst::{glib, prelude::*, subclass::prelude::*}; use gst_base::subclass::prelude::*; use once_cell::sync::Lazy; use quinn::{Connection, SendStream}; +use std::path::PathBuf; use std::sync::Mutex; static DEFAULT_SERVER_NAME: &str = "localhost"; @@ -67,6 +68,7 @@ struct Settings { timeout: u32, secure_conn: bool, use_datagram: bool, + certificate_path: Option, } impl Default for Settings { @@ -81,6 +83,7 @@ impl Default for Settings { timeout: DEFAULT_TIMEOUT, secure_conn: DEFAULT_SECURE_CONNECTION, use_datagram: false, + certificate_path: None, } } } @@ -131,6 +134,30 @@ impl ElementImpl for QuinnQuicSink { PAD_TEMPLATES.as_ref() } + + fn change_state( + &self, + transition: gst::StateChange, + ) -> Result { + if transition == gst::StateChange::NullToReady { + let settings = self.settings.lock().unwrap(); + + /* + * Fail the state change if a secure connection was requested but + * no certificate path was provided. + */ + if settings.secure_conn && settings.certificate_path.is_none() { + gst::error!( + CAT, + imp: self, + "Certificate path not provided for secure connection" + ); + return Err(gst::StateChangeError); + } + } + + self.parent_change_state(transition) + } } impl ObjectImpl for QuinnQuicSink { @@ -184,6 +211,10 @@ impl ObjectImpl for QuinnQuicSink { .blurb("Use certificates for QUIC connection. False: Insecure connection, True: Secure connection.") .default_value(DEFAULT_SECURE_CONNECTION) .build(), + glib::ParamSpecString::builder("certificate-path") + .nick("Certificate Path") + .blurb("Path where the certificate files cert.pem and privkey.pem are stored") + .build(), glib::ParamSpecBoolean::builder("use-datagram") .nick("Use datagram") .blurb("Use datagram for lower latency, unreliable messaging") @@ -233,6 +264,10 @@ impl ObjectImpl for QuinnQuicSink { "secure-connection" => { settings.secure_conn = value.get().expect("type checked upstream"); } + "certificate-path" => { + let value: String = value.get().unwrap(); + settings.certificate_path = Some(value.into()); + } "use-datagram" => { settings.use_datagram = value.get().expect("type checked upstream"); } @@ -261,6 +296,10 @@ impl ObjectImpl for QuinnQuicSink { } "timeout" => settings.timeout.to_value(), "secure-connection" => settings.secure_conn.to_value(), + "certificate-path" => { + let certpath = settings.certificate_path.as_ref(); + certpath.and_then(|file| file.to_str()).to_value() + } "use-datagram" => settings.use_datagram.to_value(), _ => unimplemented!(), } @@ -456,6 +495,7 @@ impl QuinnQuicSink { let alpns; let use_datagram; let secure_conn; + let cert_path; { let settings = self.settings.lock().unwrap(); @@ -471,14 +511,16 @@ impl QuinnQuicSink { alpns = settings.alpns.clone(); use_datagram = settings.use_datagram; secure_conn = settings.secure_conn; + cert_path = settings.certificate_path.clone(); } - let endpoint = client_endpoint(client_addr, secure_conn, alpns).map_err(|err| { - WaitError::FutureError(gst::error_msg!( - gst::ResourceError::Failed, - ["Failed to configure endpoint: {}", err] - )) - })?; + let endpoint = + client_endpoint(client_addr, secure_conn, alpns, cert_path).map_err(|err| { + WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Failed to configure endpoint: {}", err] + )) + })?; let connection = endpoint .connect(server_addr, &server_name) diff --git a/net/quic/src/utils.rs b/net/quic/src/utils.rs index 8ced1636..c6ff38b3 100644 --- a/net/quic/src/utils.rs +++ b/net/quic/src/utils.rs @@ -136,23 +136,38 @@ impl rustls::client::ServerCertVerifier for SkipServerVerification { } } -fn configure_client(secure_conn: bool, alpns: Vec) -> Result> { - if secure_conn { - Ok(ClientConfig::with_native_roots()) +fn configure_client( + secure_conn: bool, + certificate_path: Option, + alpns: Vec, +) -> Result> { + let mut crypto = if secure_conn { + let (certs, key) = read_certs_from_file(certificate_path)?; + let mut cert_store = rustls::RootCertStore::empty(); + + for cert in &certs { + cert_store.add(cert)?; + } + + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(Arc::new(cert_store)) + .with_client_auth_cert(certs, key)? } else { - let mut crypto = rustls::ClientConfig::builder() + rustls::ClientConfig::builder() .with_safe_defaults() .with_custom_certificate_verifier(SkipServerVerification::new()) - .with_no_client_auth(); - let alpn_protocols: Vec> = alpns - .iter() - .map(|x| x.as_bytes().to_vec()) - .collect::>(); - crypto.alpn_protocols = alpn_protocols; - crypto.key_log = Arc::new(rustls::KeyLogFile::new()); + .with_no_client_auth() + }; - Ok(ClientConfig::new(Arc::new(crypto))) - } + let alpn_protocols: Vec> = alpns + .iter() + .map(|x| x.as_bytes().to_vec()) + .collect::>(); + crypto.alpn_protocols = alpn_protocols; + crypto.key_log = Arc::new(rustls::KeyLogFile::new()); + + Ok(ClientConfig::new(Arc::new(crypto))) } fn read_certs_from_file( @@ -213,8 +228,8 @@ fn configure_server( certificate_path: Option, alpns: Vec, ) -> Result<(ServerConfig, Vec), Box> { - let (cert, key) = if secure_conn { - read_certs_from_file(certificate_path).unwrap() + let (certs, key) = if secure_conn { + read_certs_from_file(certificate_path)? } else { let cert = rcgen::generate_simple_self_signed(vec![server_name.into()]).unwrap(); let cert_der = cert.serialize_der().unwrap(); @@ -225,13 +240,24 @@ fn configure_server( (cert_chain, priv_key) }; - let mut crypto = rustls::ServerConfig::builder() - .with_safe_default_cipher_suites() - .with_safe_default_kx_groups() - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() - .with_no_client_auth() - .with_single_cert(cert.clone(), key)?; + let mut crypto = if secure_conn { + let mut cert_store = rustls::RootCertStore::empty(); + for cert in &certs { + cert_store.add(cert)?; + } + + let auth_client = rustls::server::AllowAnyAuthenticatedClient::new(cert_store); + rustls::ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(Arc::new(auth_client)) + .with_single_cert(certs.clone(), key) + } else { + rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(certs.clone(), key) + }?; + let alpn_protocols: Vec> = alpns .iter() .map(|x| x.as_bytes().to_vec()) @@ -245,7 +271,7 @@ fn configure_server( .max_concurrent_bidi_streams(0_u8.into()) .max_concurrent_uni_streams(1_u8.into()); - Ok((server_config, cert)) + Ok((server_config, certs)) } pub fn server_endpoint( @@ -264,9 +290,10 @@ pub fn server_endpoint( pub fn client_endpoint( client_addr: SocketAddr, secure_conn: bool, - alpn: Vec, + alpns: Vec, + certificate_path: Option, ) -> Result> { - let client_cfg = configure_client(secure_conn, alpn)?; + let client_cfg = configure_client(secure_conn, certificate_path, alpns)?; let mut endpoint = Endpoint::client(client_addr)?; endpoint.set_default_client_config(client_cfg); From 0d2f054c15c5e400b04508bd3eed36e53e68cd73 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 1 May 2024 18:12:12 +0530 Subject: [PATCH 10/12] Move net/quic to net/quinn While at it, add this to meson.build. Part-of: --- Cargo.lock | 2 +- Cargo.toml | 2 +- meson.build | 1 + meson_options.txt | 1 + net/{quic => quinn}/Cargo.toml | 4 ++-- net/{quic => quinn}/build.rs | 0 net/{quic => quinn}/src/lib.rs | 6 +++--- net/{quic => quinn}/src/quinnquicsink/imp.rs | 0 net/{quic => quinn}/src/quinnquicsink/mod.rs | 0 net/{quic => quinn}/src/quinnquicsrc/imp.rs | 0 net/{quic => quinn}/src/quinnquicsrc/mod.rs | 0 net/{quic => quinn}/src/utils.rs | 0 net/{quic => quinn}/tests/quinnquic.rs | 2 +- 13 files changed, 10 insertions(+), 8 deletions(-) rename net/{quic => quinn}/Cargo.toml (97%) rename net/{quic => quinn}/build.rs (100%) rename net/{quic => quinn}/src/lib.rs (94%) rename net/{quic => quinn}/src/quinnquicsink/imp.rs (100%) rename net/{quic => quinn}/src/quinnquicsink/mod.rs (100%) rename net/{quic => quinn}/src/quinnquicsrc/imp.rs (100%) rename net/{quic => quinn}/src/quinnquicsrc/mod.rs (100%) rename net/{quic => quinn}/src/utils.rs (100%) rename net/{quic => quinn}/tests/quinnquic.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index b4e6dc6d..7654e6cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2642,7 +2642,7 @@ dependencies = [ ] [[package]] -name = "gst-plugin-quic" +name = "gst-plugin-quinn" version = "0.13.0-alpha.1" dependencies = [ "bytes", diff --git a/Cargo.toml b/Cargo.toml index 8b68ca78..0af8a443 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ members = [ "net/webrtc", "net/webrtc/protocol", "net/webrtc/signalling", - "net/quic", + "net/quinn", "text/ahead", "text/json", diff --git a/meson.build b/meson.build index a069e709..8cf5dadb 100644 --- a/meson.build +++ b/meson.build @@ -205,6 +205,7 @@ plugins = { 'extra-deps': {'cairo-gobject': []}, }, 'gopbuffer': {'library': 'libgstgopbuffer'}, + 'quinn': {'library': 'libgstquinn'}, } if get_option('examples').allowed() diff --git a/meson_options.txt b/meson_options.txt index 7d416fa6..b9305abd 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -34,6 +34,7 @@ option('rtsp', type: 'feature', value: 'auto', description: 'Build rtsp plugin') option('rtp', type: 'feature', value: 'auto', description: 'Build rtp plugin') option('webrtc', type: 'feature', value: 'auto', yield: true, description: 'Build webrtc plugin') option('webrtchttp', type: 'feature', value: 'auto', description: 'Build webrtchttp plugin') +option('quinn', type: 'feature', value: 'auto', description: 'Build quinn plugin') # text option('textahead', type: 'feature', value: 'auto', description: 'Build textahead plugin') diff --git a/net/quic/Cargo.toml b/net/quinn/Cargo.toml similarity index 97% rename from net/quic/Cargo.toml rename to net/quinn/Cargo.toml index f2b486c3..a092b954 100644 --- a/net/quic/Cargo.toml +++ b/net/quinn/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "gst-plugin-quic" +name = "gst-plugin-quinn" version.workspace = true authors = ["Sanchayan Maity Result<(), glib::BoolError> { } gst::plugin_define!( - quic, + quinn, env!("CARGO_PKG_DESCRIPTION"), plugin_init, concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")), diff --git a/net/quic/src/quinnquicsink/imp.rs b/net/quinn/src/quinnquicsink/imp.rs similarity index 100% rename from net/quic/src/quinnquicsink/imp.rs rename to net/quinn/src/quinnquicsink/imp.rs diff --git a/net/quic/src/quinnquicsink/mod.rs b/net/quinn/src/quinnquicsink/mod.rs similarity index 100% rename from net/quic/src/quinnquicsink/mod.rs rename to net/quinn/src/quinnquicsink/mod.rs diff --git a/net/quic/src/quinnquicsrc/imp.rs b/net/quinn/src/quinnquicsrc/imp.rs similarity index 100% rename from net/quic/src/quinnquicsrc/imp.rs rename to net/quinn/src/quinnquicsrc/imp.rs diff --git a/net/quic/src/quinnquicsrc/mod.rs b/net/quinn/src/quinnquicsrc/mod.rs similarity index 100% rename from net/quic/src/quinnquicsrc/mod.rs rename to net/quinn/src/quinnquicsrc/mod.rs diff --git a/net/quic/src/utils.rs b/net/quinn/src/utils.rs similarity index 100% rename from net/quic/src/utils.rs rename to net/quinn/src/utils.rs diff --git a/net/quic/tests/quinnquic.rs b/net/quinn/tests/quinnquic.rs similarity index 97% rename from net/quic/tests/quinnquic.rs rename to net/quinn/tests/quinnquic.rs index 5a6809ec..d8d52b87 100644 --- a/net/quic/tests/quinnquic.rs +++ b/net/quinn/tests/quinnquic.rs @@ -17,7 +17,7 @@ fn init() { INIT.call_once(|| { gst::init().unwrap(); - gstquic::plugin_register_static().expect("QUIC source sink send receive tests"); + gstquinn::plugin_register_static().expect("QUIC source sink send receive tests"); }); } From 150ad7a545365419e44e1caf27812448684533c9 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 1 May 2024 19:08:46 +0530 Subject: [PATCH 11/12] net/quinn: Use separate property for certificate & private key file Part-of: --- net/quinn/src/quinnquicsink/imp.rs | 60 +++++++++++++++++--------- net/quinn/src/quinnquicsrc/imp.rs | 67 ++++++++++++++++++++---------- net/quinn/src/utils.rs | 38 ++++++++++------- 3 files changed, 109 insertions(+), 56 deletions(-) diff --git a/net/quinn/src/quinnquicsink/imp.rs b/net/quinn/src/quinnquicsink/imp.rs index c3770864..888facbd 100644 --- a/net/quinn/src/quinnquicsink/imp.rs +++ b/net/quinn/src/quinnquicsink/imp.rs @@ -68,7 +68,8 @@ struct Settings { timeout: u32, secure_conn: bool, use_datagram: bool, - certificate_path: Option, + certificate_file: Option, + private_key_file: Option, } impl Default for Settings { @@ -83,7 +84,8 @@ impl Default for Settings { timeout: DEFAULT_TIMEOUT, secure_conn: DEFAULT_SECURE_CONNECTION, use_datagram: false, - certificate_path: None, + certificate_file: None, + private_key_file: None, } } } @@ -146,11 +148,13 @@ impl ElementImpl for QuinnQuicSink { * Fail the state change if a secure connection was requested but * no certificate path was provided. */ - if settings.secure_conn && settings.certificate_path.is_none() { + if settings.secure_conn + && (settings.certificate_file.is_none() || settings.private_key_file.is_none()) + { gst::error!( CAT, imp: self, - "Certificate path not provided for secure connection" + "Certificate or private key file not provided for secure connection" ); return Err(gst::StateChangeError); } @@ -211,9 +215,13 @@ impl ObjectImpl for QuinnQuicSink { .blurb("Use certificates for QUIC connection. False: Insecure connection, True: Secure connection.") .default_value(DEFAULT_SECURE_CONNECTION) .build(), - glib::ParamSpecString::builder("certificate-path") - .nick("Certificate Path") - .blurb("Path where the certificate files cert.pem and privkey.pem are stored") + glib::ParamSpecString::builder("certificate-file") + .nick("Certificate file") + .blurb("Path to certificate chain in single file") + .build(), + glib::ParamSpecString::builder("private-key-file") + .nick("Private key file") + .blurb("Path to a PKCS8 or RSA private key file") .build(), glib::ParamSpecBoolean::builder("use-datagram") .nick("Use datagram") @@ -264,9 +272,13 @@ impl ObjectImpl for QuinnQuicSink { "secure-connection" => { settings.secure_conn = value.get().expect("type checked upstream"); } - "certificate-path" => { + "certificate-file" => { let value: String = value.get().unwrap(); - settings.certificate_path = Some(value.into()); + settings.certificate_file = Some(value.into()); + } + "private-key-file" => { + let value: String = value.get().unwrap(); + settings.private_key_file = Some(value.into()); } "use-datagram" => { settings.use_datagram = value.get().expect("type checked upstream"); @@ -296,9 +308,13 @@ impl ObjectImpl for QuinnQuicSink { } "timeout" => settings.timeout.to_value(), "secure-connection" => settings.secure_conn.to_value(), - "certificate-path" => { - let certpath = settings.certificate_path.as_ref(); - certpath.and_then(|file| file.to_str()).to_value() + "certificate-file" => { + let certfile = settings.certificate_file.as_ref(); + certfile.and_then(|file| file.to_str()).to_value() + } + "private-key-file" => { + let privkey = settings.private_key_file.as_ref(); + privkey.and_then(|file| file.to_str()).to_value() } "use-datagram" => settings.use_datagram.to_value(), _ => unimplemented!(), @@ -495,7 +511,8 @@ impl QuinnQuicSink { let alpns; let use_datagram; let secure_conn; - let cert_path; + let cert_file; + let private_key_file; { let settings = self.settings.lock().unwrap(); @@ -511,16 +528,19 @@ impl QuinnQuicSink { alpns = settings.alpns.clone(); use_datagram = settings.use_datagram; secure_conn = settings.secure_conn; - cert_path = settings.certificate_path.clone(); + cert_file = settings.certificate_file.clone(); + private_key_file = settings.private_key_file.clone(); } let endpoint = - client_endpoint(client_addr, secure_conn, alpns, cert_path).map_err(|err| { - WaitError::FutureError(gst::error_msg!( - gst::ResourceError::Failed, - ["Failed to configure endpoint: {}", err] - )) - })?; + client_endpoint(client_addr, secure_conn, alpns, cert_file, private_key_file).map_err( + |err| { + WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Failed to configure endpoint: {}", err] + )) + }, + )?; let connection = endpoint .connect(server_addr, &server_name) diff --git a/net/quinn/src/quinnquicsrc/imp.rs b/net/quinn/src/quinnquicsrc/imp.rs index 6546fb3b..23f36190 100644 --- a/net/quinn/src/quinnquicsrc/imp.rs +++ b/net/quinn/src/quinnquicsrc/imp.rs @@ -67,7 +67,8 @@ struct Settings { secure_conn: bool, caps: gst::Caps, use_datagram: bool, - certificate_path: Option, + certificate_file: Option, + private_key_file: Option, } impl Default for Settings { @@ -81,7 +82,8 @@ impl Default for Settings { secure_conn: DEFAULT_SECURE_CONNECTION, caps: gst::Caps::new_any(), use_datagram: false, - certificate_path: None, + certificate_file: None, + private_key_file: None, } } } @@ -144,11 +146,13 @@ impl ElementImpl for QuinnQuicSrc { * Fail the state change if a secure connection was requested but * no certificate path was provided. */ - if settings.secure_conn && settings.certificate_path.is_none() { + if settings.secure_conn + && (settings.certificate_file.is_none() || settings.private_key_file.is_none()) + { gst::error!( CAT, imp: self, - "Certificate path not provided for secure connection" + "Certificate or private key file not provided for secure connection" ); return Err(gst::StateChangeError); } @@ -199,9 +203,13 @@ impl ObjectImpl for QuinnQuicSrc { .blurb("Use certificates for QUIC connection. False: Insecure connection, True: Secure connection.") .default_value(DEFAULT_SECURE_CONNECTION) .build(), - glib::ParamSpecString::builder("certificate-path") - .nick("Certificate Path") - .blurb("Path where the certificate files cert.pem and privkey.pem are stored") + glib::ParamSpecString::builder("certificate-file") + .nick("Certificate file") + .blurb("Path to certificate chain in single file") + .build(), + glib::ParamSpecString::builder("private-key-file") + .nick("Private key file") + .blurb("Path to a PKCS8 or RSA private key file") .build(), glib::ParamSpecBoxed::builder::("caps") .nick("caps") @@ -259,9 +267,13 @@ impl ObjectImpl for QuinnQuicSrc { "secure-connection" => { settings.secure_conn = value.get().expect("type checked upstream"); } - "certificate-path" => { + "certificate-file" => { let value: String = value.get().unwrap(); - settings.certificate_path = Some(value.into()); + settings.certificate_file = Some(value.into()); + } + "private-key-file" => { + let value: String = value.get().unwrap(); + settings.private_key_file = Some(value.into()); } "use-datagram" => { settings.use_datagram = value.get().expect("type checked upstream"); @@ -287,9 +299,13 @@ impl ObjectImpl for QuinnQuicSrc { "caps" => settings.caps.to_value(), "timeout" => settings.timeout.to_value(), "secure-connection" => settings.secure_conn.to_value(), - "certificate-path" => { - let certpath = settings.certificate_path.as_ref(); - certpath.and_then(|file| file.to_str()).to_value() + "certificate-file" => { + let certfile = settings.certificate_file.as_ref(); + certfile.and_then(|file| file.to_str()).to_value() + } + "private-key-file" => { + let privkey = settings.private_key_file.as_ref(); + privkey.and_then(|file| file.to_str()).to_value() } "use-datagram" => settings.use_datagram.to_value(), _ => unimplemented!(), @@ -520,7 +536,8 @@ impl QuinnQuicSrc { let alpns; let use_datagram; let secure_conn; - let cert_path; + let cert_file; + let private_key_file; { let settings = self.settings.lock().unwrap(); @@ -533,16 +550,24 @@ impl QuinnQuicSrc { alpns = settings.alpns.clone(); use_datagram = settings.use_datagram; secure_conn = settings.secure_conn; - cert_path = settings.certificate_path.clone(); + cert_file = settings.certificate_file.clone(); + private_key_file = settings.private_key_file.clone(); } - let endpoint = server_endpoint(server_addr, &server_name, secure_conn, alpns, cert_path) - .map_err(|err| { - WaitError::FutureError(gst::error_msg!( - gst::ResourceError::Failed, - ["Failed to configure endpoint: {}", err] - )) - })?; + let endpoint = server_endpoint( + server_addr, + &server_name, + secure_conn, + alpns, + cert_file, + private_key_file, + ) + .map_err(|err| { + WaitError::FutureError(gst::error_msg!( + gst::ResourceError::Failed, + ["Failed to configure endpoint: {}", err] + )) + })?; let incoming_conn = endpoint.accept().await.unwrap(); diff --git a/net/quinn/src/utils.rs b/net/quinn/src/utils.rs index c6ff38b3..775d783e 100644 --- a/net/quinn/src/utils.rs +++ b/net/quinn/src/utils.rs @@ -138,11 +138,12 @@ impl rustls::client::ServerCertVerifier for SkipServerVerification { fn configure_client( secure_conn: bool, - certificate_path: Option, + certificate_file: Option, + private_key_file: Option, alpns: Vec, ) -> Result> { let mut crypto = if secure_conn { - let (certs, key) = read_certs_from_file(certificate_path)?; + let (certs, key) = read_certs_from_file(certificate_file, private_key_file)?; let mut cert_store = rustls::RootCertStore::empty(); for cert in &certs { @@ -171,7 +172,8 @@ fn configure_client( } fn read_certs_from_file( - certificate_path: Option, + certificate_file: Option, + private_key_file: Option, ) -> Result<(Vec, rustls::PrivateKey), Box> { /* * NOTE: @@ -187,13 +189,10 @@ fn read_certs_from_file( * chain in a single file. For example, this is the case of modern day * Apache and nginx. */ - let cert_file = certificate_path + let cert_file = certificate_file .clone() - .expect("Expected path to certificates be valid") - .join("fullchain.pem"); - let key_file = certificate_path - .expect("Expected path to certificates be valid") - .join("privkey.pem"); + .expect("Expected path to certificates be valid"); + let key_file = private_key_file.expect("Expected path to certificates be valid"); let certs: Vec = { let cert_file = File::open(cert_file.as_path())?; @@ -225,11 +224,12 @@ fn read_certs_from_file( fn configure_server( server_name: &str, secure_conn: bool, - certificate_path: Option, + certificate_file: Option, + private_key_file: Option, alpns: Vec, ) -> Result<(ServerConfig, Vec), Box> { let (certs, key) = if secure_conn { - read_certs_from_file(certificate_path)? + read_certs_from_file(certificate_file, private_key_file)? } else { let cert = rcgen::generate_simple_self_signed(vec![server_name.into()]).unwrap(); let cert_der = cert.serialize_der().unwrap(); @@ -279,9 +279,16 @@ pub fn server_endpoint( server_name: &str, secure_conn: bool, alpns: Vec, - certificate_path: Option, + certificate_file: Option, + private_key_file: Option, ) -> Result> { - let (server_config, _) = configure_server(server_name, secure_conn, certificate_path, alpns)?; + let (server_config, _) = configure_server( + server_name, + secure_conn, + certificate_file, + private_key_file, + alpns, + )?; let endpoint = Endpoint::server(server_config, server_addr)?; Ok(endpoint) @@ -291,9 +298,10 @@ pub fn client_endpoint( client_addr: SocketAddr, secure_conn: bool, alpns: Vec, - certificate_path: Option, + certificate_file: Option, + private_key_file: Option, ) -> Result> { - let client_cfg = configure_client(secure_conn, certificate_path, alpns)?; + let client_cfg = configure_client(secure_conn, certificate_file, private_key_file, alpns)?; let mut endpoint = Endpoint::client(client_addr)?; endpoint.set_default_client_config(client_cfg); From 096538989b77892257b624e4ff45e670ab215039 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 1 May 2024 21:38:32 +0530 Subject: [PATCH 12/12] docs: Add documentation for gst-plugin-quinn Part-of: --- docs/plugins/gst_plugins_cache.json | 318 ++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) diff --git a/docs/plugins/gst_plugins_cache.json b/docs/plugins/gst_plugins_cache.json index 3c57c068..6526cc80 100644 --- a/docs/plugins/gst_plugins_cache.json +++ b/docs/plugins/gst_plugins_cache.json @@ -3889,6 +3889,324 @@ "tracers": {}, "url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs" }, + "quinn": { + "description": "GStreamer Plugin for QUIC", + "elements": { + "quinnquicsink": { + "author": "Sanchayan Maity ", + "description": "Send data over the network via QUIC", + "hierarchy": [ + "GstQuinnQUICSink", + "GstBaseSink", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Source/Network/QUIC", + "pad-templates": { + "sink": { + "caps": "ANY", + "direction": "sink", + "presence": "always" + } + }, + "properties": { + "alpn-protocols": { + "blurb": "QUIC connection Application-Layer Protocol Negotiation (ALPN) values", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "mutable": "null", + "readable": true, + "type": "GstValueArray", + "writable": true + }, + "certificate-file": { + "blurb": "Path to certificate chain in single file", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + }, + "client-address": { + "blurb": "Address to be used by this QUIC client e.g. 127.0.0.1", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "127.0.0.1", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + }, + "client-port": { + "blurb": "Port to be used by this QUIC client e.g. 5001", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "5001", + "max": "65535", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "private-key-file": { + "blurb": "Path to a PKCS8 or RSA private key file", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + }, + "secure-connection": { + "blurb": "Use certificates for QUIC connection. False: Insecure connection, True: Secure connection.", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "server-address": { + "blurb": "Address of the QUIC server to connect to e.g. 127.0.0.1", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "127.0.0.1", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + }, + "server-name": { + "blurb": "Name of the QUIC server which is in server certificate", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "localhost", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + }, + "server-port": { + "blurb": "Port of the QUIC server to connect to e.g. 5000", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "5000", + "max": "65535", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "timeout": { + "blurb": "Value in seconds to timeout QUIC endpoint requests (0 = No timeout).", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "15", + "max": "3600", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "use-datagram": { + "blurb": "Use datagram for lower latency, unreliable messaging", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + } + }, + "rank": "marginal" + }, + "quinnquicsrc": { + "author": "Sanchayan Maity ", + "description": "Receive data over the network via QUIC", + "hierarchy": [ + "GstQuinnQUICSrc", + "GstBaseSrc", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Source/Network/QUIC", + "pad-templates": { + "src": { + "caps": "ANY", + "direction": "src", + "presence": "always" + } + }, + "properties": { + "alpn-protocols": { + "blurb": "QUIC connection Application-Layer Protocol Negotiation (ALPN) values", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "mutable": "null", + "readable": true, + "type": "GstValueArray", + "writable": true + }, + "caps": { + "blurb": "The caps of the source pad", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "ANY", + "mutable": "null", + "readable": true, + "type": "GstCaps", + "writable": true + }, + "certificate-file": { + "blurb": "Path to certificate chain in single file", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + }, + "private-key-file": { + "blurb": "Path to a PKCS8 or RSA private key file", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + }, + "secure-connection": { + "blurb": "Use certificates for QUIC connection. False: Insecure connection, True: Secure connection.", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "server-address": { + "blurb": "Address of the QUIC server e.g. 127.0.0.1", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "127.0.0.1", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + }, + "server-name": { + "blurb": "Name of the QUIC server which is in server certificate", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "localhost", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + }, + "server-port": { + "blurb": "Port of the QUIC server e.g. 5000", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "5000", + "max": "65535", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "timeout": { + "blurb": "Value in seconds to timeout QUIC endpoint requests (0 = No timeout).", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "15", + "max": "3600", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "use-datagram": { + "blurb": "Use datagram for lower latency, unreliable messaging", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + } + }, + "rank": "marginal" + } + }, + "filename": "gstquinn", + "license": "MPL", + "other-types": {}, + "package": "gst-plugin-quinn", + "source": "gst-plugin-quinn", + "tracers": {}, + "url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs" + }, "raptorq": { "description": "GStreamer RaptorQ FEC Plugin", "elements": {