From a9ffd3becd45ccaa928c5c0fa8e462178621f12f Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sun, 16 Aug 2026 12:39:42 +0200 Subject: [PATCH] feat(host): a Hello that names a format but not the capability was ignored in total silence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found on glass, and it cost the first run of the §13.2 check. The host resolved `plane="0xC9 Opus"` while every condition a reader could see was satisfiable — the operator policy was on, the session was stereo, the rate was supported, and the video bitrate left room. The reason was gate condition 1, which is deliberately NOT logged because "the client did not set the capability" is every ordinary session with every shipping client and would drown the log. That reasoning holds for the ordinary case and not for this one. A `Hello` carrying a rate or a depth but no `CLIENT_CAP_AUDIO_HIRES` is contradictory: something asked, and is being ignored. The two halves come from different places in a client — the capability from a settings toggle, the format from whatever that toggle resolved to — so they can drift apart, and an embedder that gets it wrong currently sees nothing at all. One warn, only in the contradictory case, naming both halves and saying they must be set together. --- crates/punktfunk-host/src/native/handshake.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/punktfunk-host/src/native/handshake.rs b/crates/punktfunk-host/src/native/handshake.rs index d3d3bc55..01d9a2ad 100644 --- a/crates/punktfunk-host/src/native/handshake.rs +++ b/crates/punktfunk-host/src/native/handshake.rs @@ -834,6 +834,23 @@ pub(super) async fn negotiate( // wire/sequencing changes well beyond this pass; neither is needed for 48 kHz, where the // conservative answer already lands on the longest rung. let hires_asked = hello.client_caps & punktfunk_core::quic::CLIENT_CAP_AUDIO_HIRES != 0; + // A `Hello` that names a format but does not set the capability is CONTRADICTORY, and the two + // halves come from different places in a client — the capability from a settings toggle, the + // rate and depth from whatever that toggle resolved to. Condition 1 below is deliberately not + // logged, because "no capability" is every ordinary session with every shipping client and + // would drown the log. This case is not ordinary: something asked, and is being ignored. + // + // Worth the line because it is the exact shape that cost an on-glass session its first run — + // the host resolved Opus while every visible condition looked satisfiable, and the reason was + // unlogged by design. An embedder hitting this sees nothing at all otherwise. + if !hires_asked && (hello.audio_rate_hz != 0 || hello.audio_bits != 0) { + tracing::warn!( + requested_rate_hz = hello.audio_rate_hz, + requested_bits = hello.audio_bits, + "client sent an audio format but not CLIENT_CAP_AUDIO_HIRES — ignoring it and \ + staying on Opus; the capability and the format must be set together" + ); + } let hires_allowed = pf_host_config::config().audio_hires.unwrap_or(false); // §8.4 condition 4 — what the capture path can HONESTLY deliver, asked of the device rather // than inferred from a successful open (§4.3/§4.4: both backends resample a rate they cannot