net/quinn: Make QUIC role configurable

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1575>
This commit is contained in:
Tamas Levai 2024-05-15 10:53:10 +02:00
parent 8fc652f208
commit 802ff6a67c
9 changed files with 474 additions and 160 deletions

26
Cargo.lock generated
View file

@ -2680,7 +2680,7 @@ dependencies = [
"once_cell",
"quinn",
"rcgen",
"rustls 0.23.7",
"rustls 0.23.8",
"rustls-pemfile 2.1.2",
"rustls-pki-types",
"serial_test",
@ -3837,7 +3837,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.5.7",
"socket2 0.4.10",
"tokio",
"tower-service",
"tracing",
@ -5469,16 +5469,16 @@ dependencies = [
[[package]]
name = "quinn"
version = "0.11.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb80dc034523335a9fcc34271931dd97e9132d1fb078695db500339eb72e712"
checksum = "904e3d3ba178131798c6d9375db2b13b34337d489b089fc5ba0825a2ff1bee73"
dependencies = [
"bytes",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls 0.23.7",
"rustls 0.23.8",
"thiserror",
"tokio",
"tracing",
@ -5486,15 +5486,15 @@ dependencies = [
[[package]]
name = "quinn-proto"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a063a47a1aaee4b3b1c2dd44edb7867c10107a2ef171f3543ac40ec5e9092002"
checksum = "e974563a4b1c2206bbc61191ca4da9c22e4308b4c455e8906751cc7828393f08"
dependencies = [
"bytes",
"rand",
"ring",
"rustc-hash",
"rustls 0.23.7",
"rustls 0.23.8",
"rustls-platform-verifier",
"slab",
"thiserror",
@ -5504,9 +5504,9 @@ dependencies = [
[[package]]
name = "quinn-udp"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7ad7bc932e4968523fa7d9c320ee135ff779de720e9350fee8728838551764"
checksum = "e4f0def2590301f4f667db5a77f9694fb004f82796dc1a8b1508fafa3d0e8b72"
dependencies = [
"libc",
"once_cell",
@ -5955,9 +5955,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.7"
version = "0.23.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebbbdb961df0ad3f2652da8f3fdc4b36122f568f968f45ad3316f26c025c677b"
checksum = "79adb16721f56eb2d843e67676896a61ce7a0fa622dc18d3e372477a029d2740"
dependencies = [
"once_cell",
"ring",
@ -6028,7 +6028,7 @@ dependencies = [
"jni",
"log",
"once_cell",
"rustls 0.23.7",
"rustls 0.23.8",
"rustls-native-certs 0.7.0",
"rustls-platform-verifier-android",
"rustls-webpki 0.102.4",

View file

@ -3984,6 +3984,18 @@
}
},
"properties": {
"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
},
"alpn-protocols": {
"blurb": "QUIC connection Application-Layer Protocol Negotiation (ALPN) values",
"conditionally-available": false,
@ -3995,6 +4007,32 @@
"type": "GstValueArray",
"writable": true
},
"bind-address": {
"blurb": "Address to bind QUIC client e.g. 0.0.0.0",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0.0.0.0",
"mutable": "null",
"readable": true,
"type": "gchararray",
"writable": true
},
"bind-port": {
"blurb": "Port to bind QUIC client e.g. 5001",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "65535",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
},
"certificate-file": {
"blurb": "Path to certificate chain in single file",
"conditionally-available": false,
@ -4047,6 +4085,20 @@
"type": "guint64",
"writable": true
},
"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
},
"private-key-file": {
"blurb": "Path to a PKCS8 or RSA private key file",
"conditionally-available": false,
@ -4059,6 +4111,18 @@
"type": "gchararray",
"writable": true
},
"role": {
"blurb": "QUIC connection role to use.",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "client (1)",
"mutable": "null",
"readable": true,
"type": "GstQuinnQuicRole",
"writable": true
},
"secure-connection": {
"blurb": "Use certificates for QUIC connection. False: Insecure connection, True: Secure connection.",
"conditionally-available": false,
@ -4084,7 +4148,7 @@
"writable": true
},
"server-name": {
"blurb": "Name of the QUIC server which is in server certificate",
"blurb": "Name of the QUIC server which is in server certificate in case of server role",
"conditionally-available": false,
"construct": false,
"construct-only": false,
@ -4158,6 +4222,18 @@
}
},
"properties": {
"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
},
"alpn-protocols": {
"blurb": "QUIC connection Application-Layer Protocol Negotiation (ALPN) values",
"conditionally-available": false,
@ -4169,6 +4245,32 @@
"type": "GstValueArray",
"writable": true
},
"bind-address": {
"blurb": "Address to bind QUIC client e.g. 0.0.0.0",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0.0.0.0",
"mutable": "null",
"readable": true,
"type": "gchararray",
"writable": true
},
"bind-port": {
"blurb": "Port to bind QUIC client e.g. 5001",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "65535",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint",
"writable": true
},
"caps": {
"blurb": "The caps of the source pad",
"conditionally-available": false,
@ -4193,6 +4295,34 @@
"type": "gchararray",
"writable": true
},
"keep-alive-interval": {
"blurb": "Keeps QUIC connection alive by periodically pinging the server. Value set in ms, 0 disables this feature",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "18446744073709551615",
"min": "0",
"mutable": "null",
"readable": true,
"type": "guint64",
"writable": true
},
"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
},
"private-key-file": {
"blurb": "Path to a PKCS8 or RSA private key file",
"conditionally-available": false,
@ -4205,6 +4335,18 @@
"type": "gchararray",
"writable": true
},
"role": {
"blurb": "QUIC connection role to use.",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "server (0)",
"mutable": "null",
"readable": true,
"type": "GstQuinnQuicRole",
"writable": true
},
"secure-connection": {
"blurb": "Use certificates for QUIC connection. False: Insecure connection, True: Secure connection.",
"conditionally-available": false,
@ -4230,7 +4372,7 @@
"writable": true
},
"server-name": {
"blurb": "Name of the QUIC server which is in server certificate",
"blurb": "Name of the QUIC server which is in server certificate in case of server role",
"conditionally-available": false,
"construct": false,
"construct-only": false,
@ -4287,7 +4429,23 @@
},
"filename": "gstquinn",
"license": "MPL",
"other-types": {},
"other-types": {
"GstQuinnQuicRole": {
"kind": "enum",
"values": [
{
"desc": "Server: Act as QUIC server.",
"name": "server",
"value": "0"
},
{
"desc": "Client: Act as QUIC client.",
"name": "client",
"value": "1"
}
]
}
},
"package": "gst-plugin-quinn",
"source": "gst-plugin-quinn",
"tracers": {},

38
net/quinn/src/common.rs Normal file
View file

@ -0,0 +1,38 @@
// Copyright (C) 2024, Asymptotic Inc.
// Author: Sanchayan Maity <sanchayan@asymptotic.io>
//
// 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
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
use gst::glib;
pub(crate) static DEFAULT_SERVER_NAME: &str = "localhost";
pub(crate) static DEFAULT_ADDR: &str = "127.0.0.1";
pub(crate) static DEFAULT_PORT: u16 = 5000;
pub(crate) static DEFAULT_BIND_ADDR: &str = "0.0.0.0";
pub(crate) static DEFAULT_BIND_PORT: u16 = 0;
/*
* For QUIC transport parameters
* <https://datatracker.ietf.org/doc/html/rfc9000#section-7.4>
*
* 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
* <https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids>.
*/
pub(crate) const DEFAULT_ALPN: &str = "gst-quinn";
pub(crate) const DEFAULT_TIMEOUT: u32 = 15;
pub(crate) const DEFAULT_SECURE_CONNECTION: bool = true;
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "GstQuinnQuicRole")]
pub enum QuinnQuicRole {
#[enum_value(name = "Server: Act as QUIC server.", nick = "server")]
Server,
#[enum_value(name = "Client: Act as QUIC client.", nick = "client")]
Client,
}

View file

@ -6,7 +6,11 @@
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
#![allow(clippy::non_send_fields_in_send_ty, unused_doc_comments)]
#![allow(
clippy::non_send_fields_in_send_ty,
unused_doc_comments,
unused_imports
)]
/**
* plugin-quinn:
@ -14,11 +18,17 @@
* Since: plugins-rs-0.13.0
*/
use gst::glib;
use gst::prelude::*;
mod common;
mod quinnquicsink;
mod quinnquicsrc;
mod utils;
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
#[cfg(feature = "doc")]
{
common::QuinnQuicRole::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
}
quinnquicsink::register(plugin)?;
quinnquicsrc::register(plugin)?;

View file

@ -7,8 +7,10 @@
//
// SPDX-License-Identifier: MPL-2.0
use crate::common::*;
use crate::utils::{
client_endpoint, make_socket_addr, wait, WaitError, CONNECTION_CLOSE_CODE, CONNECTION_CLOSE_MSG,
client_endpoint, make_socket_addr, server_endpoint, wait, WaitError, CONNECTION_CLOSE_CODE,
CONNECTION_CLOSE_MSG,
};
use bytes::Bytes;
use futures::future;
@ -19,23 +21,7 @@ use quinn::{Connection, SendStream};
use std::path::PathBuf;
use std::sync::Mutex;
static DEFAULT_SERVER_NAME: &str = "localhost";
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
* <https://datatracker.ietf.org/doc/html/rfc9000#section-7.4>
*
* 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
* <https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids>.
*/
const DEFAULT_ALPN: &str = "gst-quinn";
const DEFAULT_TIMEOUT: u32 = 15;
const DEFAULT_SECURE_CONNECTION: bool = true;
const DEFAULT_ROLE: QuinnQuicRole = QuinnQuicRole::Client;
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
gst::DebugCategory::new(
@ -59,12 +45,13 @@ enum State {
#[derive(Clone, Debug)]
struct Settings {
client_address: String,
client_port: u16,
server_address: String,
server_port: u16,
bind_address: String,
bind_port: u16,
address: String,
port: u16,
server_name: String,
alpns: Vec<String>,
role: QuinnQuicRole,
timeout: u32,
keep_alive_interval: u64,
secure_conn: bool,
@ -76,12 +63,13 @@ struct Settings {
impl Default for Settings {
fn default() -> Self {
Settings {
client_address: DEFAULT_CLIENT_ADDR.to_string(),
client_port: DEFAULT_CLIENT_PORT,
server_address: DEFAULT_SERVER_ADDR.to_string(),
server_port: DEFAULT_SERVER_PORT,
bind_address: DEFAULT_BIND_ADDR.to_string(),
bind_port: DEFAULT_BIND_PORT,
address: DEFAULT_ADDR.to_string(),
port: DEFAULT_PORT,
server_name: DEFAULT_SERVER_NAME.to_string(),
alpns: vec![DEFAULT_ALPN.to_string()],
role: DEFAULT_ROLE,
timeout: DEFAULT_TIMEOUT,
keep_alive_interval: 0,
secure_conn: DEFAULT_SECURE_CONNECTION,
@ -176,28 +164,28 @@ impl ObjectImpl for QuinnQuicSink {
vec![
glib::ParamSpecString::builder("server-name")
.nick("QUIC server name")
.blurb("Name of the QUIC server which is in server certificate")
.blurb("Name of the QUIC server which is in server certificate in case of server role")
.build(),
glib::ParamSpecString::builder("server-address")
glib::ParamSpecString::builder("address")
.nick("QUIC server address")
.blurb("Address of the QUIC server to connect to e.g. 127.0.0.1")
.blurb("Address of the QUIC server e.g. 127.0.0.1")
.build(),
glib::ParamSpecUInt::builder("server-port")
glib::ParamSpecUInt::builder("port")
.nick("QUIC server port")
.blurb("Port of the QUIC server to connect to e.g. 5000")
.blurb("Port of the QUIC server e.g. 5000")
.maximum(65535)
.default_value(DEFAULT_SERVER_PORT as u32)
.default_value(DEFAULT_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")
glib::ParamSpecString::builder("bind-address")
.nick("QUIC client bind address")
.blurb("Address to bind QUIC client e.g. 0.0.0.0")
.build(),
glib::ParamSpecUInt::builder("client-port")
glib::ParamSpecUInt::builder("bind-port")
.nick("QUIC client port")
.blurb("Port to be used by this QUIC client e.g. 5001")
.blurb("Port to bind QUIC client e.g. 5001")
.maximum(65535)
.default_value(DEFAULT_CLIENT_PORT as u32)
.default_value(DEFAULT_BIND_PORT as u32)
.readwrite()
.build(),
gst::ParamSpecArray::builder("alpn-protocols")
@ -205,6 +193,10 @@ impl ObjectImpl for QuinnQuicSink {
.blurb("QUIC connection Application-Layer Protocol Negotiation (ALPN) values")
.element_spec(&glib::ParamSpecString::builder("alpn-protocol").build())
.build(),
glib::ParamSpecEnum::builder_with_default("role", DEFAULT_ROLE)
.nick("QUIC role")
.blurb("QUIC connection role to use.")
.build(),
glib::ParamSpecUInt::builder("timeout")
.nick("Timeout")
.blurb("Value in seconds to timeout QUIC endpoint requests (0 = No timeout).")
@ -249,17 +241,17 @@ impl ObjectImpl for QuinnQuicSink {
"server-name" => {
settings.server_name = value.get::<String>().expect("type checked upstream");
}
"server-address" => {
settings.server_address = value.get::<String>().expect("type checked upstream");
"address" => {
settings.address = value.get::<String>().expect("type checked upstream");
}
"server-port" => {
settings.server_port = value.get::<u32>().expect("type checked upstream") as u16;
"port" => {
settings.port = value.get::<u32>().expect("type checked upstream") as u16;
}
"client-address" => {
settings.client_address = value.get::<String>().expect("type checked upstream");
"bind-address" => {
settings.bind_address = value.get::<String>().expect("type checked upstream");
}
"client-port" => {
settings.client_port = value.get::<u32>().expect("type checked upstream") as u16;
"bind-port" => {
settings.bind_port = value.get::<u32>().expect("type checked upstream") as u16;
}
"alpn-protocols" => {
settings.alpns = value
@ -274,6 +266,9 @@ impl ObjectImpl for QuinnQuicSink {
})
.collect::<Vec<_>>();
}
"role" => {
settings.role = value.get::<QuinnQuicRole>().expect("type checked upstream");
}
"timeout" => {
settings.timeout = value.get().expect("type checked upstream");
}
@ -303,20 +298,21 @@ impl ObjectImpl for QuinnQuicSink {
match pspec.name() {
"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;
"address" => settings.address.to_string().to_value(),
"port" => {
let port = settings.port as u32;
port.to_value()
}
"client-address" => settings.client_address.to_string().to_value(),
"client-port" => {
let port = settings.client_port as u32;
"bind-address" => settings.bind_address.to_string().to_value(),
"bind-port" => {
let port = settings.bind_port as u32;
port.to_value()
}
"alpn-protocols" => {
let alpns = settings.alpns.iter().map(|v| v.as_str());
gst::Array::new(alpns).to_value()
}
"role" => settings.role.to_value(),
"timeout" => settings.timeout.to_value(),
"keep-alive-interval" => settings.keep_alive_interval.to_value(),
"secure-connection" => settings.secure_conn.to_value(),
@ -353,7 +349,7 @@ impl BaseSinkImpl for QuinnQuicSink {
unreachable!("QuicSink is already started");
}
match wait(&self.canceller, self.establish_connection(), timeout) {
match wait(&self.canceller, self.init_connection(), timeout) {
Ok(Ok((c, s))) => {
*state = State::Started(Started {
connection: c,
@ -518,11 +514,12 @@ impl QuinnQuicSink {
}
}
async fn establish_connection(&self) -> Result<(Connection, Option<SendStream>), WaitError> {
async fn init_connection(&self) -> Result<(Connection, Option<SendStream>), WaitError> {
let client_addr;
let server_addr;
let server_name;
let alpns;
let role;
let use_datagram;
let keep_alive_interval;
let secure_conn;
@ -533,14 +530,15 @@ impl QuinnQuicSink {
let settings = self.settings.lock().unwrap();
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(),
format!("{}:{}", settings.bind_address, settings.bind_port).as_str(),
)?;
server_addr =
make_socket_addr(format!("{}:{}", settings.address, settings.port).as_str())?;
server_name = settings.server_name.clone();
alpns = settings.alpns.clone();
role = settings.role;
use_datagram = settings.use_datagram;
keep_alive_interval = settings.keep_alive_interval;
secure_conn = settings.secure_conn;
@ -548,31 +546,62 @@ impl QuinnQuicSink {
private_key_file = settings.private_key_file.clone();
}
let endpoint = client_endpoint(
client_addr,
secure_conn,
alpns,
cert_file,
private_key_file,
keep_alive_interval,
)
.map_err(|err| {
WaitError::FutureError(gst::error_msg!(
gst::ResourceError::Failed,
["Failed to configure endpoint: {}", err]
))
})?;
let connection;
let connection = endpoint
.connect(server_addr, &server_name)
.unwrap()
.await
.map_err(|err| {
WaitError::FutureError(gst::error_msg!(
gst::ResourceError::Failed,
["Connection error: {}", err]
))
})?;
match role {
QuinnQuicRole::Server => {
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();
connection = incoming_conn.await.map_err(|err| {
WaitError::FutureError(gst::error_msg!(
gst::ResourceError::Failed,
["Connection error: {}", err]
))
})?;
}
QuinnQuicRole::Client => {
let endpoint = client_endpoint(
client_addr,
secure_conn,
alpns,
cert_file,
private_key_file,
keep_alive_interval,
)
.map_err(|err| {
WaitError::FutureError(gst::error_msg!(
gst::ResourceError::Failed,
["Failed to configure endpoint: {}", err]
))
})?;
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| {

View file

@ -15,8 +15,8 @@
* ```bash
* gst-launch-1.0 -v -e audiotestsrc num-buffers=512 ! \
* audio/x-raw,format=S16LE,rate=48000,channels=2,layout=interleaved ! opusenc ! \
* quinnquicsink server-name="quic.net" client-address="127.0.0.1" client-port=6001 \
* server-address="127.0.0.1" server-port=6000 certificate-file="certificates/fullchain.pem" \
* quinnquicsink server-name="quic.net" bind-address="127.0.0.1" bind-port=6001 \
* address="127.0.0.1" port=6000 certificate-file="certificates/fullchain.pem" \
* private-key-file="certificates/privkey.pem"
* ```
*/

View file

@ -7,8 +7,10 @@
//
// SPDX-License-Identifier: MPL-2.0
use crate::common::*;
use crate::utils::{
make_socket_addr, server_endpoint, wait, WaitError, CONNECTION_CLOSE_CODE, CONNECTION_CLOSE_MSG,
client_endpoint, make_socket_addr, server_endpoint, wait, WaitError, CONNECTION_CLOSE_CODE,
CONNECTION_CLOSE_MSG,
};
use bytes::Bytes;
use futures::future;
@ -21,21 +23,7 @@ use quinn::{Connection, ConnectionError, ReadError, RecvStream};
use std::path::PathBuf;
use std::sync::Mutex;
static DEFAULT_SERVER_NAME: &str = "localhost";
static DEFAULT_SERVER_ADDR: &str = "127.0.0.1";
static DEFAULT_SERVER_PORT: u16 = 5000;
/*
* For QUIC transport parameters
* <https://datatracker.ietf.org/doc/html/rfc9000#section-7.4>
*
* 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
* <https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids>.
*/
const DEFAULT_ALPN: &str = "gst-quinn";
const DEFAULT_TIMEOUT: u32 = 15;
const DEFAULT_SECURE_CONNECTION: bool = true;
const DEFAULT_ROLE: QuinnQuicRole = QuinnQuicRole::Server;
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
gst::DebugCategory::new(
@ -59,11 +47,15 @@ enum State {
#[derive(Clone, Debug)]
struct Settings {
server_address: String,
server_port: u16,
address: String,
port: u16,
server_name: String,
bind_address: String,
bind_port: u16,
alpns: Vec<String>,
role: QuinnQuicRole,
timeout: u32,
keep_alive_interval: u64,
secure_conn: bool,
caps: gst::Caps,
use_datagram: bool,
@ -74,11 +66,15 @@ struct Settings {
impl Default for Settings {
fn default() -> Self {
Settings {
server_address: DEFAULT_SERVER_ADDR.to_string(),
server_port: DEFAULT_SERVER_PORT,
address: DEFAULT_ADDR.to_string(),
port: DEFAULT_PORT,
server_name: DEFAULT_SERVER_NAME.to_string(),
bind_address: DEFAULT_BIND_ADDR.to_string(),
bind_port: DEFAULT_BIND_PORT,
alpns: vec![DEFAULT_ALPN.to_string()],
role: DEFAULT_ROLE,
timeout: DEFAULT_TIMEOUT,
keep_alive_interval: 0,
secure_conn: DEFAULT_SECURE_CONNECTION,
caps: gst::Caps::new_any(),
use_datagram: false,
@ -173,17 +169,28 @@ impl ObjectImpl for QuinnQuicSrc {
vec![
glib::ParamSpecString::builder("server-name")
.nick("QUIC server name")
.blurb("Name of the QUIC server which is in server certificate")
.blurb("Name of the QUIC server which is in server certificate in case of server role")
.build(),
glib::ParamSpecString::builder("server-address")
glib::ParamSpecString::builder("address")
.nick("QUIC server address")
.blurb("Address of the QUIC server e.g. 127.0.0.1")
.build(),
glib::ParamSpecUInt::builder("server-port")
glib::ParamSpecUInt::builder("port")
.nick("QUIC server port")
.blurb("Port of the QUIC server e.g. 5000")
.maximum(65535)
.default_value(DEFAULT_SERVER_PORT as u32)
.default_value(DEFAULT_PORT as u32)
.readwrite()
.build(),
glib::ParamSpecString::builder("bind-address")
.nick("QUIC client bind address")
.blurb("Address to bind QUIC client e.g. 0.0.0.0")
.build(),
glib::ParamSpecUInt::builder("bind-port")
.nick("QUIC client port")
.blurb("Port to bind QUIC client e.g. 5001")
.maximum(65535)
.default_value(DEFAULT_BIND_PORT as u32)
.readwrite()
.build(),
gst::ParamSpecArray::builder("alpn-protocols")
@ -191,6 +198,10 @@ impl ObjectImpl for QuinnQuicSrc {
.blurb("QUIC connection Application-Layer Protocol Negotiation (ALPN) values")
.element_spec(&glib::ParamSpecString::builder("alpn-protocol").build())
.build(),
glib::ParamSpecEnum::builder_with_default("role", DEFAULT_ROLE)
.nick("QUIC role")
.blurb("QUIC connection role to use.")
.build(),
glib::ParamSpecUInt::builder("timeout")
.nick("Timeout")
.blurb("Value in seconds to timeout QUIC endpoint requests (0 = No timeout).")
@ -198,6 +209,12 @@ impl ObjectImpl for QuinnQuicSrc {
.default_value(DEFAULT_TIMEOUT)
.readwrite()
.build(),
glib::ParamSpecUInt64::builder("keep-alive-interval")
.nick("QUIC connection keep alive interval in ms")
.blurb("Keeps QUIC connection alive by periodically pinging the server. Value set in ms, 0 disables this feature")
.default_value(0)
.readwrite()
.build(),
glib::ParamSpecBoolean::builder("secure-connection")
.nick("Use secure connection")
.blurb("Use certificates for QUIC connection. False: Insecure connection, True: Secure connection.")
@ -233,11 +250,17 @@ impl ObjectImpl for QuinnQuicSrc {
"server-name" => {
settings.server_name = value.get::<String>().expect("type checked upstream");
}
"server-address" => {
settings.server_address = value.get::<String>().expect("type checked upstream");
"address" => {
settings.address = value.get::<String>().expect("type checked upstream");
}
"server-port" => {
settings.server_port = value.get::<u32>().expect("type checked upstream") as u16;
"port" => {
settings.port = value.get::<u32>().expect("type checked upstream") as u16;
}
"bind-address" => {
settings.bind_address = value.get::<String>().expect("type checked upstream");
}
"bind-port" => {
settings.bind_port = value.get::<u32>().expect("type checked upstream") as u16;
}
"alpn-protocols" => {
settings.alpns = value
@ -252,6 +275,9 @@ impl ObjectImpl for QuinnQuicSrc {
})
.collect::<Vec<String>>()
}
"role" => {
settings.role = value.get::<QuinnQuicRole>().expect("type checked upstream");
}
"caps" => {
settings.caps = value
.get::<Option<gst::Caps>>()
@ -264,6 +290,9 @@ impl ObjectImpl for QuinnQuicSrc {
"timeout" => {
settings.timeout = value.get().expect("type checked upstream");
}
"keep-alive-interval" => {
settings.keep_alive_interval = value.get().expect("type checked upstream");
}
"secure-connection" => {
settings.secure_conn = value.get().expect("type checked upstream");
}
@ -287,17 +316,24 @@ impl ObjectImpl for QuinnQuicSrc {
match pspec.name() {
"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;
"address" => settings.address.to_string().to_value(),
"port" => {
let port = settings.port as u32;
port.to_value()
}
"bind-address" => settings.bind_address.to_string().to_value(),
"bind-port" => {
let port = settings.bind_port as u32;
port.to_value()
}
"alpn-protocols" => {
let alpns = settings.alpns.iter().map(|v| v.as_str());
gst::Array::new(alpns).to_value()
}
"role" => settings.role.to_value(),
"caps" => settings.caps.to_value(),
"timeout" => settings.timeout.to_value(),
"keep-alive-interval" => settings.keep_alive_interval.to_value(),
"secure-connection" => settings.secure_conn.to_value(),
"certificate-file" => {
let certfile = settings.certificate_file.as_ref();
@ -336,7 +372,7 @@ impl BaseSrcImpl for QuinnQuicSrc {
unreachable!("QuicSrc already started");
}
match wait(&self.canceller, self.wait_for_connection(), timeout) {
match wait(&self.canceller, self.init_connection(), timeout) {
Ok(Ok((c, s))) => {
*state = State::Started(Started {
connection: c,
@ -556,11 +592,14 @@ impl QuinnQuicSrc {
};
}
async fn wait_for_connection(&self) -> Result<(Connection, Option<RecvStream>), WaitError> {
async fn init_connection(&self) -> Result<(Connection, Option<RecvStream>), WaitError> {
let server_addr;
let server_name;
let client_addr;
let alpns;
let role;
let use_datagram;
let keep_alive_interval;
let secure_conn;
let cert_file;
let private_key_file;
@ -568,41 +607,79 @@ impl QuinnQuicSrc {
{
let settings = self.settings.lock().unwrap();
server_addr = make_socket_addr(
format!("{}:{}", settings.server_address, settings.server_port).as_str(),
client_addr = make_socket_addr(
format!("{}:{}", settings.bind_address, settings.bind_port).as_str(),
)?;
server_addr =
make_socket_addr(format!("{}:{}", settings.address, settings.port).as_str())?;
server_name = settings.server_name.clone();
alpns = settings.alpns.clone();
role = settings.role;
use_datagram = settings.use_datagram;
keep_alive_interval = settings.keep_alive_interval;
secure_conn = settings.secure_conn;
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_file,
private_key_file,
)
.map_err(|err| {
WaitError::FutureError(gst::error_msg!(
gst::ResourceError::Failed,
["Failed to configure endpoint: {}", err]
))
})?;
let connection;
let incoming_conn = endpoint.accept().await.unwrap();
match role {
QuinnQuicRole::Server => {
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 connection = incoming_conn.await.map_err(|err| {
WaitError::FutureError(gst::error_msg!(
gst::ResourceError::Failed,
["Connection error: {}", err]
))
})?;
let incoming_conn = endpoint.accept().await.unwrap();
connection = incoming_conn.await.map_err(|err| {
WaitError::FutureError(gst::error_msg!(
gst::ResourceError::Failed,
["Connection error: {}", err]
))
})?;
}
QuinnQuicRole::Client => {
let endpoint = client_endpoint(
client_addr,
secure_conn,
alpns,
cert_file,
private_key_file,
keep_alive_interval,
)
.map_err(|err| {
WaitError::FutureError(gst::error_msg!(
gst::ResourceError::Failed,
["Failed to configure endpoint: {}", err]
))
})?;
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.accept_uni().await.map_err(|err| {

View file

@ -15,7 +15,7 @@
* ```bash
* gst-launch-1.0 -v -e quinnquicsrc caps=audio/x-opus server-name="quic.net" \
* certificate-file="certificates/fullchain.pem" private-key-file="certificates/privkey.pem" \
* server-address="127.0.0.1" server-port=6000 ! opusparse ! opusdec ! \
* address="127.0.0.1" port=6000 ! opusparse ! opusdec ! \
* audio/x-raw,format=S16LE,rate=48000,channels=2,layout=interleaved ! \
* audioconvert ! autoaudiosink
* ```

View file

@ -79,7 +79,9 @@ 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("quinnquicsrc use-datagram=true server-address=127.0.0.1 server-port=6000 secure-connection=false");
h1.add_parse(
"quinnquicsrc use-datagram=true address=127.0.0.1 port=6000 secure-connection=false",
);
h1.play();
@ -96,7 +98,7 @@ fn test_send_receive_with_datagram() {
});
let mut h2 = gst_check::Harness::new_empty();
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.add_parse("quinnquicsink use-datagram=true bind-address=127.0.0.1 bind-port=6001 address=127.0.0.1 port=6000 secure-connection=false");
h2.set_src_caps(gst::Caps::builder("text/plain").build());