Update docs

This commit is contained in:
2026-06-17 00:52:02 +02:00
parent 63b241cc2e
commit 2f643e4293
6 changed files with 160 additions and 31 deletions

View File

@@ -10,7 +10,7 @@ stable **C ABI** (`voicecat.h`).
┌───────────────────────────────────────────┐
macOS / iOS (Swift) │ │ Windows (C#)
┌──────────────────┐ │ libvoicecat (C++) │ ┌──────────────────┐
│ SwiftUI views │ │ ┌─────────────────────────────────────┐ │ │ WinUI/Avalonia
│ SwiftUI views │ │ ┌─────────────────────────────────────┐ │ │ WinForms (.NET 10
│ AVAudioSession │──┼─▶│ C ABI (voicecat.h) │◀─┼──│ LibraryImport │
│ Swift↔C++ interop│ │ ├─────────────────────────────────────┤ │ │ P/Invoke │
└──────────────────┘ │ │ Session / Protocol state machine │ │ └──────────────────┘
@@ -150,10 +150,14 @@ Design notes:
frames, so audio-only stays well within budget. It links a *minimal* slice of the core
(Opus encode + media send), shares the session/credentials with the host app through an
**App Group**, and re-derives its own media keys. This is detailed in [voice.md](voice.md) §9.
- **C# / Windows.** `LibraryImport` (source-generated P/Invoke, .NET 7+) over the C ABI.
Marshal the `on_event` callback as a `[UnmanagedCallersOnly]`/function-pointer to avoid
delegate lifetime pitfalls. UI in **WinUI 3** (most native) or **Avalonia** (if we later
want a single C# UI across desktop OSes).
- **C# / Windows.** `[LibraryImport]` (source-generated P/Invoke, .NET 7+) over the C ABI.
`[UnmanagedCallersOnly]` static methods for `on_event`/`on_level` to avoid delegate-lifetime
pitfalls. UI in **WinForms (.NET 10)** — chosen over WinUI 3/Avalonia for its mature,
predictable screen-reader (NVDA/JAWS/Narrator) UIA support (see roadmap.md §2).
Events are delivered via `System.Threading.Channels.Channel<VoiceCatEvent>`, drained by a
30ms `System.Windows.Forms.Timer` on the UI thread — simpler than a message-only HWND +
`PostMessage` with no meaningful latency cost. `VoiceCatClientHandle : SafeHandle` wraps
the `vc_client*` and guarantees `vc_client_destroy` runs on GC/Dispose.
## 5. Server architecture

View File

@@ -38,13 +38,28 @@ exists from M1 so the protocol can be exercised long before any GUI.
- **Exit:** a user shares mic + desktop audio; listeners control each independently.
### M4 — Native clients
- **Windows (C#/WinUI):** connect, saved-server list, channel tree, voice, text, device
pickers, meters, VAD/PTT + per-user NR controls.
- **macOS (Swift/SwiftUI):** same.
- **iOS (Swift):** AVAudioSession integration, mic permission, foreground voice; ReplayKit
broadcast extension for `SCREEN_AUDIO`.
- In-app **admin interface** (account provisioning, bans) for admin users.
- **Exit:** non-technical user installs a client, saves a server, and joins.
**Windows (C#/WinForms, .NET 10) ✓ complete 2026-06-17:**
- Connect, saved-server list (JSON, DPAPI-encrypted passwords), TOFU identity dialog.
- Channel tree (`TreeView`), user list, join (incl. password-protected channels).
- Voice: mic start/stop, mute/deafen, VAD/PTT/**always-on** mode, **VAD sensitivity slider**
(live threshold update via `vc_set_vad_threshold`), device picker, level meter.
- Per-user gain/mute/NR tuning (`PerUserTuningDialog`).
- Channel + private text chat. Activity log (screen-reader primary path).
- Explicit `AccessibleName`/`AccessibleDescription` on every control; `&` mnemonics;
`AutomationNotification` curated live announcements.
- Focus-scoped PTT (documented limitation — no system-wide hook in v1).
- Admin/moderation UI **out of scope** — needs server-side dispatch first (M5).
**macOS (Swift/SwiftUI) — pending:**
- Same feature set as Windows over the same C ABI (now stable and complete).
- AVAudioSession, mic permission, interruption/route-change handling.
**iOS (Swift) — pending:**
- AVAudioSession, mic permission, foreground voice.
- ReplayKit broadcast extension for `SCREEN_AUDIO`.
**Exit:** non-technical user installs a client, saves a server, and joins.
### M5 — Moderation, polish, and beyond
- Permissions/roles, kick/ban/server-mute, channel passwords UI.
@@ -84,6 +99,25 @@ Settled and reflected throughout the docs:
the same `ApmProcessor` interface; there is **no AEC/NS/AGC implementation at all** yet. Real
`webrtc-audio-processing` stays a tracked future swap (e.g. if/when a Linux build target
exists). (voice.md §8, §11)
- **Windows client UI framework (2026-06-17):** **WinForms** (.NET 10), not WinUI 3 or
Avalonia. Reason: Win32 HWND controls have the most mature, predictable screen-reader
(NVDA/JAWS/Narrator) support of any current .NET UI stack. WinUI 3's accessibility UIA
tree has known rough edges on .NET 10; Avalonia's accessibility story is thinner still.
This overrides the earlier WinUI/Avalonia mention in `docs/tech-stack.md §2` and
`docs/architecture.md §4`. (clients/windows/)
- **TOFU pins TLS leaf cert, not Ed25519 (2026-06-17):** the original design said to pin the
server's declared Ed25519 identity fingerprint from `ServerHello`. This is circular — the
Ed25519 key and the TLS cert are generated independently with no cryptographic binding, so
accepting/rejecting based on a value sent *inside* the channel being trust-decided is
meaningless. **Decision:** pin the TLS leaf certificate's own SHA-256 fingerprint, which is
verifiable directly from the TLS handshake before any application data is trusted. The Ed25519
value is still shown in the identity dialog for human-readable display only (informational).
See `docs/security.md §1.1`. (core/src/crypto/tofu_store.*, vc_confirm_server_identity)
- **PTT is focus-scoped in v1 (2026-06-17):** PTT hotkey capture uses `Form.KeyDown`/`KeyUp`
(works only while the VoiceCat window has focus), not a system-wide `WH_KEYBOARD_LL` hook.
Reason: a low-level keyboard hook requires escalated permissions, risks AV flagging, and is
disproportionate complexity for a v1 client. Documented in the UI as a known limitation.
Can be revisited for v2 if users request it. (clients/windows/VoiceCat.App/Forms/MainForm.cs)
## 3. Open questions

View File

@@ -29,14 +29,26 @@ media channel. Plus server identity, authentication, accounts at rest, and anti-
Self-hosting means most servers won't have a CA-signed cert for a hostname. We support
both, advertised in `ServerHello`:
1. **TOFU (Trust On First Use)** — default for hobby servers. The server has a long-lived
**Ed25519 identity key**; its fingerprint is shown to the user on first connect (like
SSH host keys / TeamSpeak server keys) and pinned locally. Subsequent connects verify
the pin; a changed key warns loudly. The TLS cert is self-signed and bound to this
identity key.
1. **TOFU (Trust On First Use)** — default for hobby servers. On first connect the client
shows an identity dialog and, if accepted, pins the value locally. Subsequent connects
verify the pin silently; a changed value warns loudly (`MISMATCH`).
2. **PKI** — a server with a domain can use a normal CA-signed cert (e.g. Let's Encrypt);
clients validate the chain conventionally. TOFU pinning still applies on top.
**What is actually pinned (M4 implementation):** the **TLS leaf certificate's SHA-256
fingerprint** — verifiable directly from the TLS handshake before any application data is
trusted. The server also declares an Ed25519 identity fingerprint in `ServerHello`, but this
value is **display-only** and is *not* the value that is pinned or verified. Reason: the TLS
cert and the Ed25519 identity key are generated independently with no cryptographic binding
between them, so pinning the self-declared Ed25519 value (sent *inside* the channel being
trust-decided) would be circular — an attacker who impersonates the server at the TLS level
would supply whatever Ed25519 value they like. Pinning the TLS cert fingerprint is the only
value that is genuinely verifiable at the moment of trust decision.
This is a known limitation of the current design. Closing it properly requires binding the
Ed25519 key into the TLS cert (e.g. as a SubjectAltName or extension), which is a planned
future improvement. Until then, clients display both values but gate on the cert fingerprint.
Client certificates are reserved for a future "key-based identity" option (see roadmap) but
are not required in v1.

View File

@@ -33,14 +33,16 @@ explicit resampling (speexdsp/libsamplerate) is only needed when a device can't
| Packaging | Swift Package + Xcode project | Core shipped as an XCFramework (device + simulator + macOS slices). |
| Future | CallKit / PushKit | For background VoIP + incoming-call UX on iOS. Post-v1. |
### Windows — C#
### Windows — C# (shipped in M4, 2026-06-17)
| Concern | Choice | Notes |
|---------|--------|-------|
| Runtime | **.NET 8+** | LTS. |
| Interop | **`LibraryImport`** (source-gen P/Invoke) over the C ABI | Marshal the event callback as a function pointer (`[UnmanagedCallersOnly]`) to avoid delegate-lifetime bugs; keep the interface "chunky" not "chatty" to minimize managed↔native transitions. |
| UI | **WinUI 3** (most native) or **Avalonia** | WinUI for a first-class Windows look; Avalonia if we later want one C# UI across desktop OSes. |
| Audio | handled by the core (miniaudio/WASAPI) | C# only drives device selection + meters. |
| Runtime | **.NET 10 LTS** (`net10.0-windows`) | In-service until 2028. |
| Interop | **`[LibraryImport]`** (source-gen P/Invoke) over the C ABI | `[UnmanagedCallersOnly]` static methods for `on_event`/`on_level`; `VoiceCatClientHandle : SafeHandle` owns the `vc_client*` lifetime. |
| Event delivery | **`System.Threading.Channels.Channel<VoiceCatEvent>`** | Single-writer/reader, unbounded; drained by a 30ms `System.Windows.Forms.Timer` on the UI thread. Simpler than a message-only HWND with no meaningful latency cost. |
| UI | **WinForms** | Chosen over WinUI 3 / Avalonia for mature, predictable NVDA/JAWS/Narrator UIA support. Win32 HWND controls have the most complete accessibility story on .NET 10 today. See roadmap.md §2. |
| Persistence | **`System.Text.Json`** (`servers.json`), **`ProtectedData`** (DPAPI) | Saved-server list in `%AppData%\VoiceCat\`; passwords DPAPI-encrypted at rest, opt-in, `CurrentUser` scope. |
| Audio | Handled by the core (miniaudio/WASAPI) | C# only drives device selection + meters. |
## 3. Server (`voicecat-server`)