Every device pending approval was called "This device" — the C ABI connect had no name parameter #243

Merged
enricobuehler merged 1 commits from worktree-pairing-friendly-device-name into main 2026-08-15 08:42:55 +00:00
Owner

The web console's outstanding-pairings view and its approve modal showed "This device" for every knocking client. The console is faithful — it renders PendingDevice.name exactly as the host received it, and the host really was told "This device".

Root cause

The name rides Hello::name, which embedders fill from client::device_name(). That resolves COMPUTERNAME (Windows-only) then HOSTNAME (a shell variable, never exported into a launchd-started GUI process) and last-resorts to the literal "This device". No Apple GUI app has either variable, and the C ABI's connect had no device-name parameter for an embedder to pass a better answer through — so every Mac, iPad, iPhone and Apple TV knocked as the placeholder, and a console with three of them pending showed three identical rows. The Apple client already knew a good name (UIDevice.current.name / Host.localizedName, used in the PIN sheet); it had no way to send it on the connect path that produces a pending knock.

Linux (/etc/hostname) and Windows were unaffected. Android passed Build.MODEL on its own JNI path — no placeholder, but the product name, so two identical tablets were still indistinguishable.

Changes

  • core: punktfunk_connect_ex10 = connect_ex9 + device_name (C ABI 20 → 21, no wire change — ex9 keeps its signature and passes a null name, which selects the old default). Names are truncated to HELLO_NAME_MAX on a character boundary (slicing a multi-byte name mid-scalar panics); covered by a unit test. device_name() now falls back to gethostname() (.local stripped, localhost rejected) before the placeholder.
  • apple: new DeviceName.current (Host.localizedName on macOS, UIDevice.current.name on iOS/tvOS, hostname fallback for the 16+ model-only answer) is sent on every connect; the two pairing sheets and the ceremony fallback now read the same source instead of three separate literals.
  • android: Settings.Global.DEVICE_NAME — the name the user typed in Settings — ahead of Build.MODEL. The "approve this device" prompt quotes the same string the connect actually knocks with.
  • web: the approve dialog names the device and its fingerprint in its description. A pre-filled field is editable text, not a statement of which knock is being approved — the fingerprint is the only thing that stays unique when two devices share a name.

Existing paired devices keep their stored trust-store names; only new knocks change. The host and console binaries are behavior-unchanged apart from the dialog copy — clients must be rebuilt to send their real names.

Verified

  • cargo fmt --check, cargo clippy -D warnings, 392/392 punktfunk-core tests
  • Apple: fresh 5-slice xcframework; swift build reaches "Build complete!" on macOS, iOS and tvOS
  • Android: :app:compileDebugKotlin BUILD SUCCESSFUL (3 ABIs clean at the API-28 floor)
  • web: codegen + build + tsc --noEmit clean

Not verified on-glass — needs a rebuilt client knocking at a host.

The web console's outstanding-pairings view and its approve modal showed "This device" for every knocking client. The console is faithful — it renders `PendingDevice.name` exactly as the host received it, and the host really was told "This device". ## Root cause The name rides `Hello::name`, which embedders fill from `client::device_name()`. That resolves `COMPUTERNAME` (Windows-only) then `HOSTNAME` (a shell variable, never exported into a launchd-started GUI process) and last-resorts to the literal `"This device"`. No Apple GUI app has either variable, and the C ABI's connect had **no device-name parameter** for an embedder to pass a better answer through — so every Mac, iPad, iPhone and Apple TV knocked as the placeholder, and a console with three of them pending showed three identical rows. The Apple client already *knew* a good name (`UIDevice.current.name` / `Host.localizedName`, used in the PIN sheet); it had no way to send it on the connect path that produces a pending knock. Linux (`/etc/hostname`) and Windows were unaffected. Android passed `Build.MODEL` on its own JNI path — no placeholder, but the *product* name, so two identical tablets were still indistinguishable. ## Changes - **core**: `punktfunk_connect_ex10` = `connect_ex9` + `device_name` (**C ABI 20 → 21**, no wire change — `ex9` keeps its signature and passes a null name, which selects the old default). Names are truncated to `HELLO_NAME_MAX` on a character boundary (slicing a multi-byte name mid-scalar panics); covered by a unit test. `device_name()` now falls back to `gethostname()` (`.local` stripped, `localhost` rejected) before the placeholder. - **apple**: new `DeviceName.current` (`Host.localizedName` on macOS, `UIDevice.current.name` on iOS/tvOS, hostname fallback for the 16+ model-only answer) is sent on every connect; the two pairing sheets and the ceremony fallback now read the same source instead of three separate literals. - **android**: `Settings.Global.DEVICE_NAME` — the name the user typed in Settings — ahead of `Build.MODEL`. The "approve this device" prompt quotes the same string the connect actually knocks with. - **web**: the approve dialog names the device and its fingerprint in its description. A pre-filled field is editable text, not a statement of *which* knock is being approved — the fingerprint is the only thing that stays unique when two devices share a name. Existing paired devices keep their stored trust-store names; only new knocks change. The host and console binaries are behavior-unchanged apart from the dialog copy — clients must be rebuilt to send their real names. ## Verified - `cargo fmt --check`, `cargo clippy -D warnings`, 392/392 punktfunk-core tests - Apple: fresh 5-slice xcframework; `swift build` reaches "Build complete!" on macOS, iOS and tvOS - Android: `:app:compileDebugKotlin` BUILD SUCCESSFUL (3 ABIs clean at the API-28 floor) - web: codegen + build + `tsc --noEmit` clean Not verified on-glass — needs a rebuilt client knocking at a host.
enricobuehler added 1 commit 2026-08-15 08:42:37 +00:00
fix(pairing): every device pending approval was called "This device"
ci / bun-nix (pull_request) Successful in 1m32s
ci / rust-arm64 (pull_request) Successful in 1m49s
apple / swift (pull_request) Successful in 2m8s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 3m29s
ci / web (pull_request) Successful in 4m1s
ci / docs-site (pull_request) Successful in 4m6s
ci / rust (pull_request) Successful in 7m8s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m44s
android / android (pull_request) Successful in 15m39s
8ae524d801
The web console shows exactly what the host was told, and the host was told
"This device" by every Apple client — so the outstanding-pairings view and the
approve dialog listed identical rows for an iPad, an Apple TV and a Mac.

The name rides `Hello::name`, which embedders fill from `client::device_name()`.
That resolves `COMPUTERNAME` (Windows-only) then `HOSTNAME` (a shell variable
never exported into a launchd-started process), and its last resort was the
literal "This device". No Apple GUI app has either variable, and the C ABI had
no device-name parameter for one to pass a better answer through, so every
Apple device fell through to the placeholder. Linux (/etc/hostname) and Windows
were unaffected; Android sent `Build.MODEL`, which names the product rather than
the unit — two of the same tablet were still indistinguishable.

- core: `punktfunk_connect_ex10` = `ex9` + `device_name` (C ABI v21, no wire
  change — `ex9` keeps its signature and passes a null name for the old
  default). Truncated to `HELLO_NAME_MAX` on a character boundary, since
  slicing a multi-byte name mid-scalar panics.
- core: `device_name()` falls back to `gethostname()` before the placeholder,
  so an embedder that passes nothing still gets a real name.
- apple: `DeviceName.current` (`Host.localizedName` / `UIDevice.current.name`,
  falling back to the hostname when 16+ answers with the bare model) is sent on
  connect, and the two pairing sheets plus the ceremony now read that one source
  instead of three separate literals.
- android: `Settings.Global.DEVICE_NAME` — the name the user typed in Settings —
  ahead of `Build.MODEL`. The "approve this device" prompt quotes the same
  string the connect knocks with, so it can't send the user looking for a row
  the console does not show.
- web: the approve dialog names the device and its fingerprint. A pre-filled
  field is editable text, not a statement of which knock is being approved.
enricobuehler scheduled this pull request to auto merge when all checks succeed 2026-08-15 08:42:51 +00:00
enricobuehler merged commit ab55dd4e39 into main 2026-08-15 08:42:55 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#243