From 60cae1e7b860fa787d401d41edc29343f61c336d Mon Sep 17 00:00:00 2001 From: Wanjohi <71614375+wanjohiryan@users.noreply.github.com> Date: Mon, 1 Jan 2024 15:34:28 +0300 Subject: [PATCH] Add ``cli`` functionality --- Cargo.lock | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 +++ build.rs | 15 +++++++ src/cli.rs | 48 ++++++++++++++++++++ src/main.rs | 69 ++++++++++++++++++++++------ src/media.rs | 2 +- 6 files changed, 250 insertions(+), 14 deletions(-) create mode 100644 build.rs create mode 100644 src/cli.rs diff --git a/Cargo.lock b/Cargo.lock index 1fb3aca..8117e51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.78" @@ -338,6 +386,62 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "clap" +version = "4.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "clap_mangen" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b5db60b3310cdb376fbeb8826e875a38080d0c61bdec0a91a3da8338948736" +dependencies = [ + "clap", + "roff", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "concurrent-queue" version = "2.4.0" @@ -1545,6 +1649,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1786,6 +1896,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.109" @@ -2057,6 +2173,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "valuable" version = "0.1.0" @@ -2092,6 +2214,8 @@ name = "warp" version = "0.1.0" dependencies = [ "anyhow", + "clap", + "clap_mangen", "env_logger", "gst-plugin-fmp4", "gstreamer", diff --git a/Cargo.toml b/Cargo.toml index 587e077..28820f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,9 @@ mp4 = "0.13" moq-transport = { git = "https://github.com/kixelated/moq-rs", version = "0.2.0" } serde_json = "1" rfc6381-codec = "0.1" +clap = { version = "4", features = ["derive"] } + +[build-dependencies] +clap = { version = "4", features = ["derive"] } +clap_mangen = "0.2" +url = "2" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..4b663fb --- /dev/null +++ b/build.rs @@ -0,0 +1,15 @@ +include!("src/cli.rs"); + +use clap::CommandFactory; + +fn main() -> Result<(), Box> { + let out_dir = std::path::PathBuf::from( + std::env::var_os("OUT_DIR").ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, "OUT_DIR not found"))?, + ); + let cmd = Config::command(); + let man = clap_mangen::Man::new(cmd); + let mut buffer: Vec = Default::default(); + man.render(&mut buffer)?; + std::fs::write(out_dir.join("moq-pub.1"), buffer)?; + Ok(()) +} \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..b49fe90 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,48 @@ +use clap::Parser; +use std::{net, path}; +use url::Url; + +#[derive(Parser, Clone, Debug)] +pub struct Config { + /// Listen for UDP packets on the given address. + #[arg(long, default_value = "[::]:0")] + pub bind: net::SocketAddr, + + /// Advertise this frame rate in the catalog (informational) + // TODO auto-detect this from the input when not provided + #[arg(long, default_value = "24")] + pub fps: u8, + + /// Advertise this bit rate in the catalog (informational) + // TODO auto-detect this from the input when not provided + #[arg(long, default_value = "1500000")] + pub bitrate: u32, + + /// Connect to the given URL starting with https:// + #[arg(value_parser = moq_url)] + pub url: Url, + + /// Use the TLS root CA at this path, encoded as PEM. + /// + /// This value can be provided multiple times for multiple roots. + /// If this is empty, system roots will be used instead + #[arg(long)] + pub tls_root: Vec, + + /// Danger: Disable TLS certificate verification. + /// + /// Fine for local development, but should be used in caution in production. + #[arg(long)] + pub tls_disable_verify: bool, +} + +fn moq_url(s: &str) -> Result { + let url = Url::try_from(s).map_err(|e| e.to_string())?; + + // Make sure the scheme is moq + if url.scheme() != "https" { + return Err("url scheme must be https:// for WebTransport".to_string()); + } + + Ok(url) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e83256a..842e2c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,16 @@ use anyhow::Context; -use url::Url; - -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::{fs, io, sync::Arc, time}; use moq_transport::cache::broadcast; +use clap::Parser; + +mod cli; +use cli::*; mod media; use media::*; -//TODO: add audio pipeline +// TODO: clap complete #[tokio::main] async fn main() -> anyhow::Result<()> { env_logger::init(); @@ -19,6 +21,8 @@ async fn main() -> anyhow::Result<()> { .finish(); tracing::subscriber::set_global_default(tracer).unwrap(); + let config = Config::parse(); + let (publisher, subscriber) = broadcast::new(""); // Create a list of acceptable root certificates. @@ -26,10 +30,27 @@ async fn main() -> anyhow::Result<()> { // Add the platform's native root certificates. // Add the platform's native root certificates. - for cert in rustls_native_certs::load_native_certs().context("could not load platform certs")? { - roots - .add(&rustls::Certificate(cert.0)) - .context("failed to add root cert")?; + if config.tls_root.is_empty() { + // Add the platform's native root certificates. + for cert in + rustls_native_certs::load_native_certs().context("could not load platform certs")? + { + roots + .add(&rustls::Certificate(cert.0)) + .context("failed to add root cert")?; + } + } else { + // Add the specified root certificates. + for root in &config.tls_root { + let root = fs::File::open(root).context("failed to open root cert file")?; + let mut root = io::BufReader::new(root); + + let root = rustls_pemfile::certs(&mut root).context("failed to read root cert")?; + anyhow::ensure!(root.len() == 1, "expected a single root cert"); + let root = rustls::Certificate(root[0].to_owned()); + + roots.add(&root).context("failed to add root cert")?; + } } let mut tls_config = rustls::ClientConfig::builder() @@ -37,20 +58,26 @@ async fn main() -> anyhow::Result<()> { .with_root_certificates(roots) .with_no_client_auth(); + // Allow disabling TLS verification altogether. + if config.tls_disable_verify { + let noop = NoCertificateVerification {}; + tls_config + .dangerous() + .set_certificate_verifier(Arc::new(noop)); + } + tls_config.alpn_protocols = vec![webtransport_quinn::ALPN.to_vec()]; // this one is important let arc_tls_config = std::sync::Arc::new(tls_config); let quinn_client_config = quinn::ClientConfig::new(arc_tls_config); let mut endpoint = - quinn::Endpoint::client(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0))?; + quinn::Endpoint::client(config.bind)?; endpoint.set_default_client_config(quinn_client_config); - let url = Url::try_from("https://localhost:4443").context("Could not get url")?; + log::info!("connecting to relay: url={}", config.url); - log::info!("connecting to relay: url={}", url); - - let session = webtransport_quinn::connect(&endpoint, &url) + let session = webtransport_quinn::connect(&endpoint, &config.url) .await .context("failed to create WebTransport session")?; @@ -65,3 +92,19 @@ async fn main() -> anyhow::Result<()> { Ok(()) } + +pub struct NoCertificateVerification {} + +impl rustls::client::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _server_name: &rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: time::SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} \ No newline at end of file diff --git a/src/media.rs b/src/media.rs index 856eb48..309f7b4 100644 --- a/src/media.rs +++ b/src/media.rs @@ -118,7 +118,7 @@ impl GST { pub async fn run(mut broadcast: broadcast::Publisher) -> anyhow::Result<()> { gst::init()?; - //FIXME: Get this value from commandline argument + //FIXME: add audio pipeline gstfmp4::plugin_register_static()?;