Repository navigation
Speak Sendspin 1.0.0-rc1 on the audio input source, with pairing tokens - #322
Merged
Merged
Conversation
The source@v1 client now uses the rc1 wire only: no trust_level, an object of pair methods with pairing_psk always offered, the rc1 pairing code flow (6-digit code, wrapped_nonce_B, rounds, pair-pending), psk_category lookup with Sentinel Fallback, client-stream/*, full client/state, single-ID fragmentation and hello-less re-handshakes. Each input gets a persisted pairing PSK and token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An input card can reveal its pairing token on demand for pasting into Music Assistant's "Pairing token" option, mirroring the output cards: read only on Show, never broadcast or logged, copied client-side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each input now stores up to 8 pairing records keyed by server, converts pre-rc1 rows on read, and pairs by code or token alongside existing records. The source handles server/unpair and revoked records, and its re-handshake, activation and logging edge cases follow the rc1 spec more closely. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An input card's Pairing disclosure lists the servers it is paired with and offers Unpair, Unpair all and Regenerate pairing token behind a confirm click; a revealed token hides after a minute or when the disclosure closes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Revocation failures are treated as success, potentially leaving credentials valid while hiding them, and socket tests can miss forbidden frames already buffered by the peer.
2 open issues
What changed in this PR
Updates the audio-input Sendspin client to 1.0.0-rc1, bringing source compatibility in line with players and extending the output-token UI pattern to inputs.
Changes:
- Adopts rc1 messaging and pairing flows, with updated protocol and cryptographic tests.
- Adds persistent pairing tokens, multiple server records, migration, and revocation APIs.
- Adds input pairing controls with timed token disclosure and confirmation for destructive actions.
| File | Description |
|---|---|
test/universal_proxy/sendspin/wire_test.exs |
Verifies rc1 framing and message payloads. |
test/universal_proxy/sendspin/pairing_token_test.exs |
Checks token encoding against the reference vector. |
test/universal_proxy/sendspin/pairing_test.exs |
Covers rc1 cryptography, retries, and pairing methods. |
test/universal_proxy/audio/input/store_test.exs |
Tests token persistence, record eviction, and migration. |
test/universal_proxy/audio/input/source_test.exs |
Covers authentication, revocation, and re-handshake behavior. |
test/universal_proxy/audio/input/source_pairing_test.exs |
Exercises code and token pairing flows. |
test/universal_proxy/audio/input/server_test.exs |
Tests pairing APIs and secret-free summaries. |
test/universal_proxy_web/live/audio_live_test.exs |
Tests disclosure, expiry, and confirmation controls. |
test/support/sendspin_source_peer.ex |
Updates the scripted peer for rc1 exchanges. |
test/support/sendspin_pairing_server.ex |
Implements rc1 server-side pairing test flows. |
lib/universal_proxy/sendspin/pairing.ex |
Implements rc1 pairing methods and retry rounds. |
lib/universal_proxy/sendspin/pairing_token.ex |
Encodes version-0 pairing tokens. |
lib/universal_proxy/audio/input/store.ex |
Persists credentials and manages multiple pairing records. |
lib/universal_proxy/audio/input/server.ex |
Coordinates token access, revocation, and public summaries. |
lib/universal_proxy/audio/input.ex |
Exposes pairing-management APIs. |
lib/universal_proxy_web/live/audio_live.ex |
Adds input token disclosure and pairing-management controls. |
Review effort: Balanced
A revocation that returns {:error, _} now keeps the prompt armed and the token shown, with an error on the card. Source tests assert silence via Peer.refute_frames!/2 so buffered frames are checked too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Security-critical authentication and credential-migration changes need independent protocol review, while Music Assistant’s native pairing flow and Pi execution still require human validation.
No open issues 🎉
2 resolved since last review
Review effort: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Moves the USB audio input's Sendspin source client (source@v1, pure Elixir) to the 1.0.0-rc1 wire. Before this change, aiosendspin 10.3.0 flagged it as non-compliant and tolerated it only because
allow_noncompliant_clientsdefaults to on; a strict server would reject it. The player already speaks rc1 (via sendspin-cpp), so this brings inputs in line.Compatibility: the source is now rc1-only. It won't interoperate with aiosendspin ≤9.1.1 servers (Music Assistant ≤2.10.6), the same as the players already. MA 2.10.7 stable pins aiosendspin 10.2.0, which is verified below.
Wire
client/hellono longer sends the removedtrust_level.supported_pair_methodsis now an object offeringpairing_psk(required by the spec) anddynamic_pairing_code.client-stream/startandclient-stream/endreplace the underscore names.client/statecarries the fullsourceobject.activitiesvalues follow the spec's unknown-value rule.Pairing (rc1)
dynamic_pairing_code/static_pairing_code, with a fixed 6-digit code under thesendspin-pairing-code-derive-v1label.client/pair-confirmcarrieswrapped_nonce_B; theroundnumber is part ofsid;client/pair-retryrounds andclient/pair-pendingare supported;pair/abortuses the rc1 reasons.psk_category(lt/pr/sn). A miss on the initial handshake takes the Sentinel Fallback.server_idmismatch now fails the handshake.SP:0+ base32(client key ‖ pairing PSK), checked against the spec vector. The full Pairing PSK flow is implemented, both re-keying a live session and dialing with the token.server/unpair: honoured in a long-term session: the record is removed, then goodbyeunpairedand close. Code pairing works again on an already-paired input; the consent hold stays.psk_idrecomputed.UI (Audio tab, input cards)
Hardening from review
client-stream/endis deferred while a re-key is in progress.Verification
mise run test(3851 passed), format, credo --strict, dialyzer, Argus: all clean.allow_noncompliant_clients=False, 0 non-compliance lines in every run:server/unpairfollowed by re-pairing by code;🤖 Generated with Claude Code