Skip to content

librespot builds rustls without ring - #478

Closed
kixelated wants to merge 1 commit into
mainfrom
quest/m2/adoption/librespot
Closed

kixelated wants to merge 1 commit into
mainfrom
quest/m2/adoption/librespot

Conversation

@kixelated

@kixelated kixelated commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Claims quest/m2/adoption/librespot.md.

Problem

librespot's rustls features always compile ring, so spotifyd, ncspot, and other apps on its crates can't swap in another rustls provider without also building ring. The quest's goal is a merged upstream PR that lets them pick.

On dev (99b6088), ring comes in three ways under either rustls-tls-* feature:

  1. librespot-core turns on hyper-rustls's ring feature.
  2. librespot-oauth uses reqwest's rustls-tls-* features and oauth2/rustls-tls, both of which turn on reqwest's ring.
  3. hyper-proxy2 0.1.0 (last release January 2024) brings in hyper-rustls 0.26, rustls 0.22, and webpki 0.22, all tied to ring. This is a second rustls stack beside 0.23.

tokio-tungstenite is already provider-neutral. At runtime, librespot already uses an installed default provider; the cost is building ring at all.

Approach

The upstream change is prepared and tested but not posted, since it goes outside the moq-dev and kixelated orgs. Nothing lands in rind from this PR. It stays a draft to hold the claim until the maintainer decides on posting.

Two commits, per librespot's CONTRIBUTING (one major change per commit):

  1. core: replace hyper-proxy2 with hyper-http-proxy. hyper-http-proxy is hyper-proxy2's maintained fork (1.2.0, 2026-08-12, about 8.4M recent downloads) with the same API. It uses hyper-rustls with default-features = false and gets its provider from ClientConfig::builder(). This drops seven crates from Cargo.lock.
  2. core, oauth: add rustls features without a crypto provider. Adds rustls-tls-native-roots-no-provider and rustls-tls-webpki-roots-no-provider to core, oauth, audio, connect, discovery, metadata and playback, named after reqwest's features. The existing rustls-tls-* features become <x>-no-provider plus ring (hyper-rustls/ring, reqwest/rustls-tls-*), so their behaviour is unchanged.
    • hyper-rustls hides its with_native_roots() and with_webpki_roots() shorthands without its ring or aws-lc-rs feature. A ring-free build caught this. http_client.rs now builds the ClientConfig the way those shorthands do (ClientConfig::builder() and ConfigBuilderExt), which needs rustls as a direct optional dependency of core. It was already in the tree.
    • oauth2/rustls-tls is dropped: it only turned on reqwest/rustls-tls (ring with webpki roots) on top of the reqwest features already set.
    • The upstream CI cargo hack discovery step excludes the new features, or --each-feature would pair them with native-tls and hit the mutual-exclusion compile_error!.
    • COMPILING.md and CHANGELOG.md are updated.

AI policy. librespot has no written AI policy (CONTRIBUTING.md, SECURITY.md, PR and issue templates checked). It has no ban like Servo's, so the quest goes ahead. Maintainer kingosticks has complained about "walls of AI text" (librespot-org/librespot#1649) and "competing LLMs" (librespot-org/librespot#1725). The draft below is therefore short, plain, and discloses the AI help in one line.

Choices made without asking:

  • The root librespot crate (the binary) gets no -no-provider features. A binary built that way has no provider. Downstream apps depend on the sub-crates (spotifyd and ncspot do), and those all get the features.
  • Missing provider: it panics with rustls's own message, as reqwest's no-provider features do, rather than returning a new error. librespot's HttpClient already unwrap()s client creation (librespot-org/librespot#1811 is changing that separately).
  • The draft names no provider. Per the adoption epic, core-stack PRs stay provider-neutral.
  • The hyper-proxy2 swap rides in the same PR as its own commit. On its own it doesn't get ring out of the tree, and splitting it would double the asks on a maintainer with little time.

Validation

On librespot dev at 99b6088, patch head 1f01f2f:

  • Upstream CI equivalents with RUSTFLAGS=-D warnings, in a nix shell with ALSA and cargo-hack:

    • cargo fmt --all -- --check
    • cargo build --locked --workspace --examples
    • cargo test --locked --workspace: all pass
    • the discovery cargo hack check --each-feature step as patched, excluding with-dns-sd, which needs the Avahi compat library
    • cargo clippy for librespot with each of native-tls, rustls-tls-native-roots and rustls-tls-webpki-roots (plus rodio-backend,with-libmdns)
    • cargo clippy for core, oauth, connect, playback and discovery with each -no-provider feature

    Upstream's --each-feature audio-backend sweep was not run: it needs every audio system library, and the patch doesn't touch audio.

  • A downstream test app on the patched crates fetches https://apresolve.spotify.com through librespot_core::http_client::HttpClient, both directly and through a local HTTP CONNECT proxy (exercising hyper-http-proxy's TLS tunnel), then builds a reqwest::Client the way librespot-oauth does:

    • rustls-tls-native-roots-no-provider with rind-rustls 0.23.0 (crates.io) installed: passes, and no ring, aws-lc-rs or aws-lc-sys appears in cargo tree -e normal,build.
    • rustls-tls-native-roots (ring, unchanged path), no provider installed: passes.
    • native-tls: passes.
    • -no-provider with nothing installed: panics with rustls's "Call CryptoProvider::install_default() before this point" message, as intended.
  • Codex review of the upstream branch (quest review --base origin/dev --size S), recorded under Review.

Not run: an armv7 or aarch64 build (the quest's Raspberry Pi motivation). The patch has no architecture-specific code, and rind's own CI covers those targets.

Impact

None on rind's API or wire format. Upstream gains two opt-in features per crate. Existing features, defaults and MSRV (1.85) are unchanged. hyper-http-proxy declares 1.70.

Alternatives

  • Replace hyper-proxy2 with hyper-util's client-proxy Tunnel under hyper-rustls's HttpsConnector. That needs no new crate but rewrites the proxy code. librespot-org/librespot#1768 (open, SOCKS5 support) already rewrites it with its own connector. Note that #1768 sets rustls = { features = ["ring"] }, so if it lands first, its connector needs the same provider-neutral treatment.
  • Make the existing rustls-tls-* features provider-neutral and add a rustls-ring feature. Fewer feature names, but downstream apps built on today's features would panic at runtime. Rejected.

Upstream PR draft

To post from a fork of librespot-org/librespot, against dev. Reword freely; it is written to read as the poster's own.

Title: Let applications pick their rustls crypto provider

The rustls features always compile ring, even when the application installs a different rustls provider (aws-lc-rs, or a pure-Rust one). librespot already uses whichever provider is installed, so ring gets built for nothing.

This PR adds `rustls-tls-native-roots-no-provider` and `rustls-tls-webpki-roots-no-provider`, named after reqwest's features. They leave the provider to the application, which installs one with `CryptoProvider::install_default()` before using librespot. The existing `rustls-tls-*` features now mean the no-provider feature plus ring, so nothing changes for current users.

To get ring fully out of the tree, the first commit also replaces `hyper-proxy2` with `hyper-http-proxy`, its maintained fork with the same API. hyper-proxy2 hasn't had a release since January 2024 and pulls in a second rustls (0.22) that always uses ring. This drops seven crates from `Cargo.lock`. It overlaps with #1768, which removes hyper-proxy2 with its own connector. Whichever lands second can adapt; I'm happy to rebase onto it.

Tested:
- `cargo check --workspace --no-default-features` with each TLS feature
- `cargo clippy` and `cargo fmt --check`
- a small app on the librespot crates fetching apresolve through `HttpClient`, both directly and through a local HTTP CONNECT proxy. It works with native-tls, with `rustls-tls-native-roots` (ring, unchanged), and with `rustls-tls-native-roots-no-provider` plus a third-party provider, where `cargo tree -i ring` is empty. With no provider installed it panics with rustls's "call `CryptoProvider::install_default()`" message, the same as reqwest.

I wrote this with an AI assistant (Claude) and reviewed and tested it myself.

Upstream change

Against librespot-org/librespot@99b6088 (dev), two commits, with Cargo.lock left out. To apply:

  1. git am the patch.
  2. Run cargo check once. It rewrites Cargo.lock, dropping hyper-proxy2, hyper-rustls 0.26, rustls 0.22, rustls-native-certs 0.7, rustls-pemfile, rustls-webpki 0.102, tokio-rustls 0.25 and webpki 0.22, and adding hyper-http-proxy and core's rustls entry.
  3. Commit the lockfile. Upstream CI uses --locked.
librespot-upstream.patch
From d3faa64c66b56d3de47355b2ff49c2fc357a63ad Mon Sep 17 00:00:00 2001
From: Luke Curley <kixelated@gmail.com>
Date: Sat, 7 Nov 2026 00:24:30 -0800
Subject: [PATCH 1/2] core: replace hyper-proxy2 with hyper-http-proxy

hyper-proxy2 0.1.0 (January 2024) depends on hyper-rustls 0.26, so every
rustls build carried a second rustls (0.22) plus webpki 0.22, both tied
to ring. hyper-http-proxy is its maintained fork with the same API, on
rustls 0.23, and it leaves the crypto provider to rustls's default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---
 CHANGELOG.md            | 1 +
 core/Cargo.toml         | 8 ++++----
 core/src/http_client.rs | 2 +-
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ce6444..5adc1f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - [core] Made `SpotifyId::to_base62`, `SpotifyId::to_base16`, `FileId::to_base16`, `SpotifyUri::to_id`, `SpotifyUri::to_uri` infallible (breaking)
 - [oauth] Made the URLs clickable in a terminal
+- [core] Replace the unmaintained `hyper-proxy2` with `hyper-http-proxy`, which drops a second rustls (0.22) and its ring-only dependencies from rustls builds
 
 ### Fixed
 
diff --git a/core/Cargo.toml b/core/Cargo.toml
index db963ca..b72f3d8 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -17,20 +17,20 @@ default = ["native-tls"]
 # Note: Validation is in oauth since it's compiled first in the dependency tree.
 native-tls = [
     "dep:hyper-tls",
-    "hyper-proxy2/tls",
+    "hyper-http-proxy/native-tls",
     "librespot-oauth/native-tls",
     "tokio-tungstenite/native-tls",
 ]
 rustls-tls-native-roots = [
     "__rustls",
-    "hyper-proxy2/rustls",
+    "hyper-http-proxy/rustls-tls-native-roots",
     "hyper-rustls/native-tokio",
     "librespot-oauth/rustls-tls-native-roots",
     "tokio-tungstenite/rustls-tls-native-roots",
 ]
 rustls-tls-webpki-roots = [
     "__rustls",
-    "hyper-proxy2/rustls-webpki",
+    "hyper-http-proxy/rustls-tls-webpki-roots",
     "hyper-rustls/webpki-tokio",
     "librespot-oauth/rustls-tls-webpki-roots",
     "tokio-tungstenite/rustls-tls-webpki-roots",
@@ -62,7 +62,7 @@ httparse = "1.10"
 http = "1.3"
 http-body-util = "0.1"
 hyper = { version = "1.6", features = ["http1", "http2"] }
-hyper-proxy2 = { version = "0.1", default-features = false }
+hyper-http-proxy = { version = "1.2", default-features = false }
 hyper-rustls = { version = "0.27", default-features = false, features = [
     "http1",
     "http2",
diff --git a/core/src/http_client.rs b/core/src/http_client.rs
index 03af57e..ed4503d 100644
--- a/core/src/http_client.rs
+++ b/core/src/http_client.rs
@@ -12,7 +12,7 @@ use governor::{
 use http::{Uri, header::HeaderValue};
 use http_body_util::{BodyExt, Full};
 use hyper::{HeaderMap, Request, Response, StatusCode, body::Incoming, header::USER_AGENT};
-use hyper_proxy2::{Intercept, Proxy, ProxyConnector};
+use hyper_http_proxy::{Intercept, Proxy, ProxyConnector};
 use hyper_util::{
     client::legacy::{Client, ResponseFuture, connect::HttpConnector},
     rt::TokioExecutor,
-- 
2.56.0


From 1f01f2f45c226b6d72328e6676455cc49d5b153b Mon Sep 17 00:00:00 2001
From: Luke Curley <kixelated@gmail.com>
Date: Sat, 7 Nov 2026 00:27:59 -0800
Subject: [PATCH 2/2] core, oauth: add rustls features without a crypto
 provider

rustls-tls-native-roots and rustls-tls-webpki-roots compile ring into
every application that uses them. An application that installs another
rustls provider (aws-lc-rs, or a pure-Rust one) already has librespot
use it, but still builds ring, along with its C and assembly.

Add rustls-tls-native-roots-no-provider and
rustls-tls-webpki-roots-no-provider, following reqwest's names. They
leave the provider to the application, which installs it with
CryptoProvider::install_default before using librespot. The existing
features become these plus ring, so their behaviour is unchanged.

hyper-rustls hides its with_native_roots/with_webpki_roots shorthands
unless its ring or aws-lc-rs feature is on, so the HTTP client builds
its ClientConfig the same way they do, from ClientConfig::builder().
oauth2's rustls-tls feature only enabled reqwest/rustls-tls (ring and
webpki roots) on top of the reqwest features already set, so it is
dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---
 .github/workflows/build.yml |  2 +-
 CHANGELOG.md                |  1 +
 COMPILING.md                |  2 ++
 audio/Cargo.toml            |  2 ++
 connect/Cargo.toml          |  2 ++
 core/Cargo.toml             | 22 +++++++++++++++++-----
 core/src/http_client.rs     | 21 +++++++++++++++------
 discovery/Cargo.toml        |  2 ++
 metadata/Cargo.toml         |  2 ++
 oauth/Cargo.toml            | 16 ++++++++++++----
 oauth/src/lib.rs            |  2 +-
 playback/Cargo.toml         | 10 ++++++++++
 12 files changed, 67 insertions(+), 17 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9ab2ce9..c35375c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -92,7 +92,7 @@ jobs:
         run: >
           cargo hack check -p librespot-discovery --each-feature --exclude-all-features
           --include-features native-tls
-          --exclude-features rustls-tls-native-roots,rustls-tls-webpki-roots
+          --exclude-features rustls-tls-native-roots,rustls-tls-webpki-roots,rustls-tls-native-roots-no-provider,rustls-tls-webpki-roots-no-provider
 
       - name: Build binary with default features
         run: cargo build --frozen
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5adc1f5..cd94a3f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - [playback] Add `SetQueue` player event, emitting when the queue changes (context loaded, track added to queue, or queue set via Spotify Connect). Gated behind `ConnectConfig::emit_set_queue_events`
 - [examples] Obtain an access token via OAuth to establish a new session and retrieve the stored credentials
 - [playback] Add track's artists to playback loading verbose logs
+- [core] Add `rustls-tls-native-roots-no-provider` and `rustls-tls-webpki-roots-no-provider` features, which use rustls without enabling `ring`, so applications can install their own crypto provider
 
 ### Changed
 
diff --git a/COMPILING.md b/COMPILING.md
index e6119df..ef4a357 100644
--- a/COMPILING.md
+++ b/COMPILING.md
@@ -145,6 +145,8 @@ Uses a Rust-based TLS implementation with certificate authority (CA) verificatio
 - Certificate trust is independent of host system
 - Best for reproducible builds, containers, or embedded systems
 
+Both use rustls's `ring` crypto provider. Applications that depend on the librespot crates and pick their own provider (such as `aws-lc-rs`) enable `rustls-tls-native-roots-no-provider` or `rustls-tls-webpki-roots-no-provider` instead, and install the provider with `rustls::crypto::CryptoProvider::install_default` before using librespot.
+
 **When to choose rustls-tls:**
 - You want to avoid external OpenSSL dependencies
 - You're building for reproducible/deterministic builds
diff --git a/audio/Cargo.toml b/audio/Cargo.toml
index ce2e0d7..5b31672 100644
--- a/audio/Cargo.toml
+++ b/audio/Cargo.toml
@@ -16,6 +16,8 @@ default = ["native-tls"]
 native-tls = ["librespot-core/native-tls"]
 rustls-tls-native-roots = ["librespot-core/rustls-tls-native-roots"]
 rustls-tls-webpki-roots = ["librespot-core/rustls-tls-webpki-roots"]
+rustls-tls-native-roots-no-provider = ["librespot-core/rustls-tls-native-roots-no-provider"]
+rustls-tls-webpki-roots-no-provider = ["librespot-core/rustls-tls-webpki-roots-no-provider"]
 
 [dependencies]
 librespot-core = { version = "0.8.0", path = "../core", default-features = false }
diff --git a/connect/Cargo.toml b/connect/Cargo.toml
index bc5267f..d95afc6 100644
--- a/connect/Cargo.toml
+++ b/connect/Cargo.toml
@@ -16,6 +16,8 @@ default = ["native-tls"]
 native-tls = ["librespot-core/native-tls"]
 rustls-tls-native-roots = ["librespot-core/rustls-tls-native-roots"]
 rustls-tls-webpki-roots = ["librespot-core/rustls-tls-webpki-roots"]
+rustls-tls-native-roots-no-provider = ["librespot-core/rustls-tls-native-roots-no-provider"]
+rustls-tls-webpki-roots-no-provider = ["librespot-core/rustls-tls-webpki-roots-no-provider"]
 
 [dependencies]
 librespot-core = { version = "0.8.0", path = "../core", default-features = false }
diff --git a/core/Cargo.toml b/core/Cargo.toml
index b72f3d8..995f68f 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -22,22 +22,34 @@ native-tls = [
     "tokio-tungstenite/native-tls",
 ]
 rustls-tls-native-roots = [
+    "rustls-tls-native-roots-no-provider",
+    "hyper-rustls/ring",
+    "librespot-oauth/rustls-tls-native-roots",
+]
+rustls-tls-webpki-roots = [
+    "rustls-tls-webpki-roots-no-provider",
+    "hyper-rustls/ring",
+    "librespot-oauth/rustls-tls-webpki-roots",
+]
+# The same without a crypto provider: the application installs one with
+# `rustls::crypto::CryptoProvider::install_default` before using librespot.
+rustls-tls-native-roots-no-provider = [
     "__rustls",
     "hyper-http-proxy/rustls-tls-native-roots",
     "hyper-rustls/native-tokio",
-    "librespot-oauth/rustls-tls-native-roots",
+    "librespot-oauth/rustls-tls-native-roots-no-provider",
     "tokio-tungstenite/rustls-tls-native-roots",
 ]
-rustls-tls-webpki-roots = [
+rustls-tls-webpki-roots-no-provider = [
     "__rustls",
     "hyper-http-proxy/rustls-tls-webpki-roots",
     "hyper-rustls/webpki-tokio",
-    "librespot-oauth/rustls-tls-webpki-roots",
+    "librespot-oauth/rustls-tls-webpki-roots-no-provider",
     "tokio-tungstenite/rustls-tls-webpki-roots",
 ]
 
 # Internal features - these are not meant to be used by end users
-__rustls = []
+__rustls = ["dep:rustls"]
 
 [dependencies]
 librespot-oauth = { version = "0.8.0", path = "../oauth", default-features = false }
@@ -66,7 +78,6 @@ hyper-http-proxy = { version = "1.2", default-features = false }
 hyper-rustls = { version = "0.27", default-features = false, features = [
     "http1",
     "http2",
-    "ring",
 ], optional = true }
 hyper-tls = { version = "0.6", optional = true }
 hyper-util = { version = "0.1", default-features = false, features = [
@@ -88,6 +99,7 @@ protobuf-json-mapping = "3.7"
 quick-xml = { version = "0.38", features = ["serialize"] }
 rand = { version = "0.9", default-features = false, features = ["thread_rng"] }
 rsa = "0.9"
+rustls = { version = "0.23", default-features = false, features = ["std"], optional = true }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 sha1 = { version = "0.10", features = ["oid"] }
diff --git a/core/src/http_client.rs b/core/src/http_client.rs
index ed4503d..274817d 100644
--- a/core/src/http_client.rs
+++ b/core/src/http_client.rs
@@ -22,9 +22,11 @@ use thiserror::Error;
 use url::Url;
 
 #[cfg(all(feature = "__rustls", not(feature = "native-tls")))]
-use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
+use hyper_rustls::{ConfigBuilderExt, HttpsConnector, HttpsConnectorBuilder};
 #[cfg(all(feature = "native-tls", not(feature = "__rustls")))]
 use hyper_tls::HttpsConnector;
+#[cfg(all(feature = "__rustls", not(feature = "native-tls")))]
+use rustls::ClientConfig;
 
 use crate::{
     Error,
@@ -149,11 +151,18 @@ impl HttpClient {
 
         #[cfg(all(feature = "__rustls", not(feature = "native-tls")))]
         let https_connector = {
-            #[cfg(feature = "rustls-tls-native-roots")]
-            let tls = HttpsConnectorBuilder::new().with_native_roots()?;
-            #[cfg(feature = "rustls-tls-webpki-roots")]
-            let tls = HttpsConnectorBuilder::new().with_webpki_roots();
-            tls.https_or_http().enable_http1().enable_http2().build()
+            // `ClientConfig::builder` takes the process-wide provider, which the
+            // application installs when it builds without ring.
+            #[cfg(feature = "rustls-tls-native-roots-no-provider")]
+            let config = ClientConfig::builder().with_native_roots()?;
+            #[cfg(feature = "rustls-tls-webpki-roots-no-provider")]
+            let config = ClientConfig::builder().with_webpki_roots();
+            HttpsConnectorBuilder::new()
+                .with_tls_config(config.with_no_client_auth())
+                .https_or_http()
+                .enable_http1()
+                .enable_http2()
+                .build()
         };
 
         #[cfg(all(feature = "native-tls", not(feature = "__rustls")))]
diff --git a/discovery/Cargo.toml b/discovery/Cargo.toml
index c7cf4f9..39231a5 100644
--- a/discovery/Cargo.toml
+++ b/discovery/Cargo.toml
@@ -21,6 +21,8 @@ with-libmdns = ["dep:libmdns"]
 native-tls = ["librespot-core/native-tls"]
 rustls-tls-native-roots = ["librespot-core/rustls-tls-native-roots"]
 rustls-tls-webpki-roots = ["librespot-core/rustls-tls-webpki-roots"]
+rustls-tls-native-roots-no-provider = ["librespot-core/rustls-tls-native-roots-no-provider"]
+rustls-tls-webpki-roots-no-provider = ["librespot-core/rustls-tls-webpki-roots-no-provider"]
 
 [dependencies]
 librespot-core = { version = "0.8.0", path = "../core", default-features = false }
diff --git a/metadata/Cargo.toml b/metadata/Cargo.toml
index d2b06f4..ba12c3d 100644
--- a/metadata/Cargo.toml
+++ b/metadata/Cargo.toml
@@ -16,6 +16,8 @@ default = ["native-tls"]
 native-tls = ["librespot-core/native-tls"]
 rustls-tls-native-roots = ["librespot-core/rustls-tls-native-roots"]
 rustls-tls-webpki-roots = ["librespot-core/rustls-tls-webpki-roots"]
+rustls-tls-native-roots-no-provider = ["librespot-core/rustls-tls-native-roots-no-provider"]
+rustls-tls-webpki-roots-no-provider = ["librespot-core/rustls-tls-webpki-roots-no-provider"]
 
 [dependencies]
 librespot-core = { version = "0.8.0", path = "../core", default-features = false }
diff --git a/oauth/Cargo.toml b/oauth/Cargo.toml
index 5412e0f..bc00197 100644
--- a/oauth/Cargo.toml
+++ b/oauth/Cargo.toml
@@ -15,15 +15,23 @@ default = ["native-tls"]
 # TLS backends (mutually exclusive - compile-time checks in src/lib.rs)
 native-tls = ["oauth2/native-tls", "reqwest/native-tls"]
 rustls-tls-native-roots = [
-    "__rustls",
-    "oauth2/rustls-tls",
+    "rustls-tls-native-roots-no-provider",
     "reqwest/rustls-tls-native-roots",
 ]
 rustls-tls-webpki-roots = [
-    "__rustls",
-    "oauth2/rustls-tls",
+    "rustls-tls-webpki-roots-no-provider",
     "reqwest/rustls-tls-webpki-roots",
 ]
+# The same without a crypto provider: the application installs one with
+# `rustls::crypto::CryptoProvider::install_default` before using librespot.
+rustls-tls-native-roots-no-provider = [
+    "__rustls",
+    "reqwest/rustls-tls-native-roots-no-provider",
+]
+rustls-tls-webpki-roots-no-provider = [
+    "__rustls",
+    "reqwest/rustls-tls-webpki-roots-no-provider",
+]
 
 # Internal features - these are not meant to be used by end users
 __rustls = []
diff --git a/oauth/src/lib.rs b/oauth/src/lib.rs
index 9610e37..f62a7ec 100644
--- a/oauth/src/lib.rs
+++ b/oauth/src/lib.rs
@@ -48,7 +48,7 @@ use url::Url;
 
 #[cfg(all(feature = "native-tls", feature = "__rustls"))]
 compile_error!(
-    "Feature \"native-tls\" is mutually exclusive with \"rustls-tls-native-roots\" and \"rustls-tls-webpki-roots\". Enable only one."
+    "Feature \"native-tls\" is mutually exclusive with the \"rustls-tls-*\" features. Enable only one."
 );
 
 #[cfg(not(any(feature = "native-tls", feature = "__rustls")))]
diff --git a/playback/Cargo.toml b/playback/Cargo.toml
index dd916b5..2bd026c 100644
--- a/playback/Cargo.toml
+++ b/playback/Cargo.toml
@@ -45,6 +45,16 @@ rustls-tls-webpki-roots = [
     "librespot-audio/rustls-tls-webpki-roots",
     "librespot-metadata/rustls-tls-webpki-roots",
 ]
+rustls-tls-native-roots-no-provider = [
+    "librespot-core/rustls-tls-native-roots-no-provider",
+    "librespot-audio/rustls-tls-native-roots-no-provider",
+    "librespot-metadata/rustls-tls-native-roots-no-provider",
+]
+rustls-tls-webpki-roots-no-provider = [
+    "librespot-core/rustls-tls-webpki-roots-no-provider",
+    "librespot-audio/rustls-tls-webpki-roots-no-provider",
+    "librespot-metadata/rustls-tls-webpki-roots-no-provider",
+]
 
 [dependencies]
 librespot-audio = { version = "0.8.0", path = "../audio", default-features = false }
-- 
2.56.0

Waiting

  • Maintainer: decide whether and how to post the upstream PR above. An agent can't: it is outward-facing outside moq-dev and kixelated, and the text should be the poster's own words. Options:

    • (a) fork librespot-org/librespot to kixelated, push the patch, and open the PR with the draft, reworded as you like. Recommended: the change stands on its own (dead dependency, duplicate rustls stack, reqwest-style features) and names no provider.
    • (b) first ask on librespot-org/librespot#1768 for its connector to stay provider-neutral. That adds a round-trip on a slow queue (#1768 has been idle since 2026-09-23).
    • (c) drop the quest. Not recommended.

    Done when the upstream PR is open. The quest then waits on librespot-org/librespot: review and merge <PR URL>.

Models

Size: S
Implementer: Claude Code, claude-opus-5-5, high effort

Review

No rind change, so quest review on this branch has nothing to review. The upstream librespot branch was reviewed instead: Codex, gpt-6.1-sol, medium effort, session 01a1b52b-0158-7170-aee1-c0fe1f6a1d29, round 1. Head 1f01f2f45c226b6d72328e6676455cc49d5b153b against base 99b6088bf3ea028f90192676ac1a337fa4b2ed12 (origin/dev). Score 10/10, 0 findings: "No actionable regressions were found in the specified merge-base-to-head diff after tracing TLS feature propagation and the replacement proxy connector". Codex ran no builds; the validation above covers that. Record skipped (no upstream PR), so it is written here by hand.

Suggested follow-ups

  • Plan: spotifyd's rustls build uses rind once a librespot release ships the -no-provider features. spotifyd is what Raspberry Pi users actually run, and it is where rind's armv7 and aarch64 speed reaches them. Block it on this quest.
  • Drop: a separate quest for the hyper-proxy2 swap alone. It is already a commit in this patch, and on its own it doesn't remove ring.

(Written by Claude Opus 5.5 with high)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Closing: the maintainer dropped this quest on 2026-11-07 (let projects pull rind rather than send them patches). moq-dev/rind#471 deletes it with the others.

(Written by Claude Opus 5.5 with high)

@kixelated kixelated closed this Nov 7, 2026
@kixelated
kixelated deleted the quest/m2/adoption/librespot branch November 7, 2026 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant