Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/linux-credential-worker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Linux credential worker foundation

Issue #722, dependent on #721. This component is not started by the unavailable Linux runtime. It does not activate Bluetooth, approve pairing, inject input or change existing synchronous model startup. Production ownership and startup migration remain separate work.

One worker owns one dedicated blocking thread and admits at most one outstanding load, save or delete. Overlapping callers receive `Busy`; there is no retry queue or per-request thread spawning. A caller-supplied timeout must be greater than zero and at most 30 seconds. Device identifiers are limited to 128 bytes and tokens to 4096 bytes; empty identifiers/tokens are rejected. Backend errors become the fixed `StorageUnavailable` result, with no raw native error or credential logging.

Timeout and dropping a future invalidate its result. Generation invalidation discards stale results; queued work checks cancellation before calling the backend. Cancellation can race with starting a native call and cannot interrupt it or roll back a save/delete already underway. A timed-out write may still commit. Callers must never publish/approve a credential from a cancelled or failed operation and must reconcile persisted state before retrying a replacement.

Admission remains busy until the native call returns, even after caller timeout. Shutdown stops admission, invalidates results and closes the channel without joining the thread. A stuck native call may therefore leave a detached thread until process exit. The future runtime must own exactly one worker and must not recreate workers on timeout; otherwise repeated recreation would defeat the thread bound. Restart is the recovery path for a permanently stuck worker.

Fake-store tests cover save/load/delete, request and response bounds, sanitized errors, timeout while a call remains blocked, busy admission, discarded stale results, recovery, dropped futures and non-blocking shutdown. They perform no real keyring operations. Physical Secret Service qualification and subscriber isolation are still required before enabling secure Linux pairing; see [credential storage gates](linux-credentials.md).
21 changes: 21 additions & 0 deletions docs/linux-credentials.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Linux credential storage foundation

Issue #720. This is storage hardening on `linux-support`, not an enabled secure Linux runtime.

The pinned keyring 4.1.6 default `v1` adapter selects Secret Service on Linux, Windows Credential Manager on Windows, and Keychain Services on macOS. Linux continues to use the existing service `com.enaboapps.switchify.pc.pairing` and device-ID lookup keys. No plaintext, kernel-keyring or memory fallback is added. Dependencies, state schema and other platforms' storage selection are unchanged.

The Linux wrapper serializes operations on each storage instance. Save succeeds only when a nonempty token is written and read back unchanged. Missing credentials remain distinct from an inaccessible store; empty stored values fail closed. Backend errors are replaced with a fixed remediation message, never raw D-Bus text, credential attributes or tokens. Unlock the desktop Secret Service keyring; ensure a Secret Service provider is installed/running, then restart Switchify PC. The pinned keyring adapter caches initial store initialization, so merely retrying after an initial service failure may not recover in the same process.

A failed verification does not delete the credential: a write may have succeeded before a read failed, and destructive rollback could erase existing access. A replacement write can therefore change the backend even when verification reports failure; it is not a transaction or a durability guarantee. Future pairing integration must not approve/publish a token on any storage error and must account for replacement failure recovery.

The existing model restores pairing tokens transactionally into the protocol engine. If any load fails, no tokens are activated, and saved pairing metadata is preserved when settings are persisted. Restarting after storage recovery can restore access. Ordinary missing entries retain existing behavior; this wrapper does not infer that every missing entry means the whole store failed.

## Validation and remaining gates

Fake-store tests cover successful write verification, recreated wrapper reads, empty credentials, mismatched read-back, save/load/delete failures, non-destructive verification failure and model metadata preservation/recovery. They never access a real keyring or Bluetooth device. Recreating a fake adapter is not physical persistence evidence.

Before enabling Linux pairing, manually qualify an unlocked store, locked/missing provider, permission denial, application restart, logout/login, failed replacement and explicit forgetting on each supported desktop. No new readiness probe writes test credentials or prompts at startup. Bounded/off-main-thread credential operations and recovery UX belong to production runtime integration; this wrapper retains the synchronous storage API.

Subscriber isolation remains a separate unresolved gate. This change cannot approve Linux pairing or inject input.

Reference: [keyring 4.1.6 v1 adapter](https://docs.rs/keyring/4.1.6/keyring/v1/index.html).
19 changes: 19 additions & 0 deletions docs/linux-read-responses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Linux peer-scoped reply transport

Issue #724. Optional BLE transport extension, retaining protocol v1 messages and authentication. This mailbox is not yet wired to a live runtime.

## Why notifications cannot carry Linux replies

In BlueZ 5.72, `sock_io_read` passes AcquireNotify data to `send_notification_to_devices`, which iterates subscribed device states. Device identity on a BlueR writer is not a delivery boundary. Refusing a competing writer after BlueZ accepts its CCC subscription does not close that race. This is source evidence of broadcast behavior, not merely missing hardware evidence: [pinned BlueZ implementation](https://github.com/bluez/bluez/blob/5.72/src/gatt-database.c#L2445).

## Negotiation and wire contract

A Linux server using this transport adds `responseTransport: "read-v1"` to discovery status and exposes read-only characteristic `7a78f7ec-1d6d-4d92-9ef0-1f89d3db21f4` under the existing Switchify service. Existing service/RX/TX/status identifiers are unchanged. Updated clients choose polling only after reading this marker; absent marker retains existing notification behavior. Unsupported marker values must fail closed. Linux read-v1 servers never place protocol responses on TX, even for older clients. Old clients cannot complete pairing and must update; there is no sensitive notification fallback.

Each offset-zero read consumes one existing v1 JSON/base64 frame (at most 180 bytes); an empty value means no reply. Nonzero offset reads return the same snapshot for ATT long-read assembly. Clients run only one read at a time. A read failure terminates the session rather than retrying a possibly consumed frame. No ACK/retransmission mechanism is added. Clients retain normal bounded protocol reassembly and request deadlines. Reads are directed ATT responses associated by BlueZ with the requesting connection, not notifications.

The runtime must associate RX and mailbox ownership with the BlueZ request peer, reject competing peers, serialize access and clear the mailbox/reassembler/input on disconnect before allowing reuse of an address. Idle mailbox reads never claim ownership. Every async response carries the mailbox generation; results from previous sessions are rejected. Queue admission is atomic and capped at 256 KiB encoded data plus one 180-byte read snapshot. On overflow the runtime must tear down the session, not silently lose a reply.

## Qualification

Automated fake-peer tests cover competing reads without consumption, long-read offsets at MTU 23, normal protocol framing, bounded queues and generation cleanup. No credential is broadcast by this design. Real Android read interoperability, disconnect races and input cleanup still need a supervised test before calling the build usable. Multi-adapter coverage remains a broader release-quality gate, but notification broadcast isolation is no longer the intended trust boundary.
35 changes: 35 additions & 0 deletions docs/linux-x11-development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Experimental X11 control build

Issue #726; depends on the credential and read-response stack (#721, #723, #725) and Switchify Remote read-v1 support (#157 in switchify-remote). This is a supervised development build, not a Linux production release or a claim of completed hardware qualification.

## Build and opt in

Build as an ordinary user on Ubuntu 24.04/Mint 22 X11, with the development prerequisites from the Linux CI job installed (WebKitGTK 4.1, GTK3, D-Bus, X11/XKB, OpenSSL and AppIndicator headers), Node 24 and Rust 1.97.1:

```sh
npm ci
npm run tauri build -- --debug --no-bundle
SWITCHIFY_LINUX_EXPERIMENTAL=1 SWITCHIFY_LINUX_ADAPTER=hci0 ./src-tauri/target/debug/switchify-pc
```

Without the explicit environment opt-in the existing unavailable Linux runtime remains in use. Requires `XDG_SESSION_TYPE=x11`, a display and no Wayland display environment. The selected adapter must already be powered and advertising-capable. No power, pairing database, D-Bus policy or device permissions are changed. No root UI or input helper is used. Do not use this development mode at the lock screen, unattended, or on a shared desktop; active-seat/lock-screen qualification is not complete.

Use an updated Remote that supports `responseTransport: read-v1`. Compare the pairing verification code in both apps before approving. Approval writes and verifies the credential through Secret Service, persists metadata, then activates and exposes the reply only through the peer-scoped read mailbox. Failures do not approve the device; uncertain replacement writes require restarting and pairing again. Missing/locked Secret Service providers are not bypassed. Startup restoration still uses the existing synchronous model path, so a provider that hangs during startup remains a known limitation.

## Usable slice and limits

Basic text, keyboard shortcuts/modifiers, streamed typing, pointer movement, clicks, drag, scroll and media commands use the existing input adapter. A reduced pointer profile disables repeat, dwell, window management, display navigation and switch forwarding. The Controls UI exposes pointer speed without unsupported repeat/dwell controls. Tray/overlay behavior stays at the existing Linux foundation (visible main window and no overlays). Exact layout, Unicode, scaling and media behavior need manual X11 validation.

One runtime thread owns input and protocol processing. RX admission is bounded to 64 requests of at most 512 bytes. Read replies are bounded by the mailbox. Peer changes, disconnect observations, a two-second missing-poll lease, authentication failure, queue failures, forgetting and exit invalidate stale work and release tracked input. A detected wall-clock pause above three seconds also invalidates the session. Generation-scoped UI approval prevents an old approval from authorizing a replacement connection. Native credential waits are bounded; input is released before waiting. Expired pairing requests are removed.

Radio/daemon failure is fail-closed; automatic service re-registration is not implemented. Restart after such a failure. The app never removes unrelated registrations or pairings. Shutdown requests cleanup and waits up to one second; an irrecoverably blocked native input call is not claimed cancellable. Physical disconnect/address-reuse ordering, cleanup under suspend/lock, Secret Service persistence, and competing ATT clients still require qualification. The read mailbox removes notification broadcasting as the reply mechanism, not all Bluetooth trust concerns.

## Supervised acceptance test

1. Start the opt-in desktop build; confirm the ordinary-user UI reports readiness without granting unavailable capabilities.
2. On the updated phone, discover the PC, compare the verification code and approve on the desktop. Verify pairing completes (not merely that services were discovered).
3. Focus a disposable text editor yourself. Send short test text and a shortcut; test pointer movement, click, scroll, then drag/release.
4. Disconnect while a modifier/drag is held and confirm it is released. Repeat with phone Bluetooth off, app exit and reconnect.
5. Restart the desktop and verify saved pairing reconnects. Repeat with a locked keyring and confirm an actionable failure, never silent approval.

Automated tests use fake input only. No actual typing, clicking, scrolling or pointer movement is part of the test suite. Record exact desktop, BlueZ, adapter, Android and Remote versions with the manual results; never include tokens or typed private text.
Loading