diff --git a/crates/Cargo.lock b/crates/Cargo.lock index 11f471f64..b71992866 100644 --- a/crates/Cargo.lock +++ b/crates/Cargo.lock @@ -5943,6 +5943,7 @@ dependencies = [ "rand 0.10.1", "reqwest", "rusqlite", + "rustls", "serde", "serde_json", "tokio", @@ -5998,7 +5999,6 @@ dependencies = [ "mdns-sd", "rupnp", "rust_cast", - "rustls", "serde", "serde_json", "thiserror 2.0.18", diff --git a/crates/qbz-app/Cargo.toml b/crates/qbz-app/Cargo.toml index fe80866c1..2e5ff6e1b 100644 --- a/crates/qbz-app/Cargo.toml +++ b/crates/qbz-app/Cargo.toml @@ -24,3 +24,6 @@ chrono = { workspace = true } md-5 = { workspace = true } base64 = { workspace = true } rand = "0.10" + +# TLS provider selection +rustls = { version = "0.23", features = ["aws_lc_rs"] } diff --git a/crates/qbz-app/src/lib.rs b/crates/qbz-app/src/lib.rs index 9e375788d..56e5084ed 100644 --- a/crates/qbz-app/src/lib.rs +++ b/crates/qbz-app/src/lib.rs @@ -10,3 +10,20 @@ pub mod session_store; pub mod shell; pub mod settings; pub mod user_data; + +/// Install the rustls process-level `CryptoProvider` (aws-lc-rs) exactly once. +/// +/// Idempotent (a second call is a no-op); harmless if some other component +/// already installed a default (`install_default` then returns Err, ignored). +pub fn ensure_crypto_provider() { + use std::sync::Once; + static INIT: Once = Once::new(); + INIT.call_once(|| { + if rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .is_err() + { + log::debug!("[app] rustls CryptoProvider already installed"); + } + }); +} diff --git a/crates/qbz-cast/Cargo.toml b/crates/qbz-cast/Cargo.toml index 0c1331e43..d772065a0 100644 --- a/crates/qbz-cast/Cargo.toml +++ b/crates/qbz-cast/Cargo.toml @@ -8,12 +8,6 @@ description = "Casting support for QBZ - Chromecast, DLNA" # Chromecast rust_cast = "0.21" -# TLS provider selection. rust_cast uses rustls; in the multi-crate binary both -# the aws-lc-rs and ring providers end up enabled (reqwest pulls ring), so -# rustls can't auto-pick one and panics on connect. We install aws-lc-rs -# explicitly before connecting (see `ensure_crypto_provider`). -rustls = { version = "0.23", features = ["aws_lc_rs"] } - # mDNS discovery mdns-sd = "0.11" diff --git a/crates/qbz-cast/src/chromecast/thread.rs b/crates/qbz-cast/src/chromecast/thread.rs index ecfc30fde..00f2477d9 100644 --- a/crates/qbz-cast/src/chromecast/thread.rs +++ b/crates/qbz-cast/src/chromecast/thread.rs @@ -75,9 +75,6 @@ impl ChromecastHandle { /// Connect to a Chromecast device pub fn connect(&self, ip: String, port: u16) -> Result<(), CastError> { - // rust_cast opens a rustls TLS channel; make sure a CryptoProvider is - // installed first (the worker thread would otherwise panic). - crate::ensure_crypto_provider(); let (reply_tx, reply_rx) = mpsc::channel(); self.sender .send(CastCommand::Connect { diff --git a/crates/qbz-cast/src/dlna/device.rs b/crates/qbz-cast/src/dlna/device.rs index 6c363f21f..9d90a48f4 100644 --- a/crates/qbz-cast/src/dlna/device.rs +++ b/crates/qbz-cast/src/dlna/device.rs @@ -60,9 +60,6 @@ pub struct DlnaConnection { impl DlnaConnection { /// Connect to a DLNA device and discover service URLs pub async fn connect(device: DiscoveredDlnaDevice) -> Result { - // Defensive: the device-description fetch may go over TLS in some - // setups; ensure a rustls CryptoProvider is installed (idempotent). - crate::ensure_crypto_provider(); let device_url: Uri = device .url .parse() diff --git a/crates/qbz-cast/src/lib.rs b/crates/qbz-cast/src/lib.rs index d81106c5c..f13c8b52b 100644 --- a/crates/qbz-cast/src/lib.rs +++ b/crates/qbz-cast/src/lib.rs @@ -38,25 +38,3 @@ pub use dlna::{ /// Cast device type alias for backwards compatibility pub type CastDevice = CastDeviceConnection; -/// Install the rustls process-level `CryptoProvider` (aws-lc-rs) exactly once. -/// -/// `rust_cast` opens a TLS control channel to the Chromecast via rustls. In the -/// full QBZ binary BOTH the `aws-lc-rs` and `ring` providers are enabled (reqwest -/// pulls `ring`, rust_cast pulls `aws-lc-rs`), so rustls can't auto-select one -/// and panics with "Could not automatically determine the process-level -/// CryptoProvider". Installing one explicitly before any TLS use fixes it. -/// -/// Idempotent (a second call is a no-op); harmless if some other component -/// already installed a default (`install_default` then returns Err, ignored). -pub fn ensure_crypto_provider() { - use std::sync::Once; - static INIT: Once = Once::new(); - INIT.call_once(|| { - if rustls::crypto::aws_lc_rs::default_provider() - .install_default() - .is_err() - { - log::debug!("[cast] rustls CryptoProvider already installed"); - } - }); -} diff --git a/crates/qbz/src/main.rs b/crates/qbz/src/main.rs index 8feab7e6a..747ce77f7 100644 --- a/crates/qbz/src/main.rs +++ b/crates/qbz/src/main.rs @@ -8120,17 +8120,8 @@ fn main() -> Result<(), Box> { // Large/XL (and shrink RAM at Small). Set before any artwork job runs. crate::artwork::set_ui_scale_factor(ui_scale_factor); - // Install the rustls process-level CryptoProvider ONCE, before ANY TLS use. - // The full binary compiles BOTH rustls providers (aws-lc-rs via qbz-cast's - // rust_cast, ring via reqwest), so rustls cannot auto-select one and panics - // ("Could not automatically determine the process-level CryptoProvider") the - // first time a TLS connection opens. Previously this was only installed lazily - // on the cast paths, so QConnect's tokio-tungstenite WebSocket (it opens a - // rustls TLS channel on its own worker thread) panicked when the user joined a - // session WITHOUT ever casting. Installing here at startup covers every TLS - // consumer — QConnect, cast, reqwest — regardless of which connects first. - // Idempotent (the cast paths still call it; the second call is a no-op). - qbz_cast::ensure_crypto_provider(); + // Install the rustls process-level `CryptoProvider` + qbz_app::ensure_crypto_provider(); let tokio_rt = tokio::runtime::Runtime::new()?; let _enter = tokio_rt.enter(); diff --git a/crates/qbzd/src/main.rs b/crates/qbzd/src/main.rs index a4285f2c3..9f7ba4013 100644 --- a/crates/qbzd/src/main.rs +++ b/crates/qbzd/src/main.rs @@ -256,6 +256,9 @@ enum ConfigCmd { Path, Show { #[arg(long)] json: bool } } #[tokio::main] async fn main() { + // Install the rustls process-level `CryptoProvider` + qbz_app::ensure_crypto_provider(); + let cli = Cli::parse(); let code = match cli.cmd { Cmd::Version { json } => {