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

@@ -4,13 +4,11 @@ Auto-loaded each session. This is the **map**: build commands, architecture at a
where everything is. For the *working method* read [`AGENTS.md`](AGENTS.md); for *what's done where everything is. For the *working method* read [`AGENTS.md`](AGENTS.md); for *what's done
and what's next* read [`PROGRESS.md`](PROGRESS.md); for *design* read [`docs/`](docs/). and what's next* read [`PROGRESS.md`](PROGRESS.md); for *design* read [`docs/`](docs/).
> **One-line status:** M3 (multi-stream, per-channel tuning) is complete, plus a follow-up > **One-line status:** M4 (Windows WinForms C# client) is complete — connect, saved servers,
> pass closing the device enumeration / VAD-PTT input gate / stereo playback / WASAPI loopback > TOFU identity pinning, channel tree, voice (VAD/PTT/always-on + VAD threshold slider +
> gaps it left open (`ctest --test-dir build/m1-dev` green — 12/12 tests, including > per-user tuning), text chat, device pickers, level meters. `ctest --preset m1-dev` green —
> `test_vad_ptt_devices`, and `vccli --voice`/`--list-devices` manually verified live). Real > 14/14 tests. `dotnet build` green — 0 warnings. macOS/iOS Swift client and M5 (moderation)
> `webrtc-audio-processing` (AEC/NS/AGC) is still unbuilt — no working Windows/MSVC port > are next. See [`PROGRESS.md`](PROGRESS.md).
> upstream — so v1 ships a lightweight energy/RMS VAD instead. Next up is **M4** (native
> clients). See [`PROGRESS.md`](PROGRESS.md).
VoiceCat = self-hosted native voice & text chat (TeamSpeak/Mumble-style). Plain TCP (control) VoiceCat = self-hosted native voice & text chat (TeamSpeak/Mumble-style). Plain TCP (control)
+ UDP (media), no WebRTC, encrypted by default. A shared C++ core (`libvoicecat`) drives + UDP (media), no WebRTC, encrypted by default. A shared C++ core (`libvoicecat`) drives

View File

@@ -53,7 +53,14 @@ up instantly. Newest status at the top.
(mutex lock, heap allocation, blocking `sendto` on the miniaudio real-time callback (mutex lock, heap allocation, blocking `sendto` on the miniaudio real-time callback
thread) — predates this work, documented but not fixed; fixing it needs the lock-free thread) — predates this work, documented but not fixed; fixing it needs the lock-free
ring-buffer hand-off `docs/architecture.md §3` specifies, a separate, larger refactor. ring-buffer hand-off `docs/architecture.md §3` specifies, a separate, larger refactor.
- **Next:** **M4 — native clients** (Windows C#, macOS/iOS Swift). See `docs/roadmap.md §M4`. - **Done:** **M4 — Windows WinForms C# client** ✓ complete (2026-06-17). Full details in the
M4 section below. `ctest --preset m1-dev`**14/14 tests** green. `dotnet build` — 0
warnings/errors across all three C# projects. Manually verified: saved servers, TOFU
first-connect dialog, channel tree, join, voice (VAD/PTT/always-on + sensitivity slider +
per-user gain/mute/NR), text chat (channel + private), device pickers, level meter.
- **Next:** macOS/iOS Swift client (M4 continued) and/or **M5** (admin UI, moderation, kick/
ban). The C ABI is complete and stable through M4 — both directions are unblocked. See
`docs/roadmap.md §M4M5`.
--- ---
@@ -63,7 +70,7 @@ up instantly. Newest status at the top.
- [x] **M1 — Control plane** ✓ complete (2026-06-15) - [x] **M1 — Control plane** ✓ complete (2026-06-15)
- [x] **M2 — Voice, single stream** ✓ complete (2026-06-16) - [x] **M2 — Voice, single stream** ✓ complete (2026-06-16)
- [x] **M3 — Multi-stream & per-channel tuning** ✓ complete (2026-06-16) - [x] **M3 — Multi-stream & per-channel tuning** ✓ complete (2026-06-16)
- [ ] **M4 — Native clients** (Windows C#, macOS/iOS Swift) ← next - [~] **M4 — Native clients** Windows WinForms ✓ (2026-06-17); macOS/iOS Swift pending
- [ ] **M5 — Moderation, polish, beyond** (perms, bans, DRED; then file transfer, E2EE, …) - [ ] **M5 — Moderation, polish, beyond** (perms, bans, DRED; then file transfer, E2EE, …)
--- ---
@@ -349,6 +356,78 @@ flags), and `vccli --voice --input-mode vad` connecting + streaming without inci
--- ---
## M4 — Windows WinForms C# client ✓ (completed 2026-06-17)
**Exit criterion:** ✓ `ctest --preset m1-dev` — **14/14 tests** green (existing 12 + 2 new
C++ tests: `test_channel_user_list_abi`, `test_tofu_flow`). `dotnet build` — 0 warnings/errors.
Manually verified: connect, TOFU first-connect dialog, channel tree, join, voice, text, device
pickers, level meter. Accessibility: explicit `AccessibleName`/`AccessibleDescription` on every
control, `&` mnemonics on every button, activity-log `ListBox` as screen-reader record.
**New C++ ABI surface** (all additive, backward-compatible):
- `vc_list_channels` / `vc_list_users` / `vc_list_user_streams` — pull-based snapshot getters
for the channel tree + user list; `session_model_mu_` added for cross-thread safety.
`SessionModel::apply_snapshot`/`apply_channel_event` fixed to populate `parent_id`,
`password_protected`, `max_users` (were permanently zeroed despite the struct declaring them).
- `vc_join_channel(channel_id, password)` — join with optional password; server replies via
new `VC_EVENT_JOIN_RESULT`.
- `VC_EVENT_SERVER_IDENTITY` + `vc_confirm_server_identity(accept)` — TOFU gate that blocks
`io_thread_` until the UI approves or rejects. Pins the TLS leaf-cert SHA-256 fingerprint
(verifiable directly at handshake), **not** the declared Ed25519 value (see docs/security.md
§1.1 for why — the TLS cert and Ed25519 key are generated independently, no binding).
`vc_get_server_identity_display` exposes the Ed25519 fingerprint for human-readable display.
- `vc_config::tofu_store_path` — optional per-user pin file path; defaults to a relative
`"./voicecat_tofu_pins.txt"` so existing tests need no change.
- `TcpAcceptor` now dual-stacks (IPv6 + IPv4 fallback) so `localhost` → `::1` on Windows
connects correctly without forcing users to type `127.0.0.1`.
- `VC_INPUT_ALWAYS_ON = 2` in `vc_input_mode` — transmit unconditionally (no VAD gate).
- `vc_set_vad_threshold(float)` — live VAD threshold update; `EnergyVadProcessor` stores it
atomically so the RT capture path reads without a lock or allocation.
**New C++ tests:**
- `test_channel_user_list_abi` — snapshot getters, `parent_id`/`password_protected`/`max_users`
regression, per-user stream list, invalid-user-id error, double-free idempotency.
- `test_tofu_flow` — first-connect blocks until confirmed; reject doesn't persist; reconnect to
same identity reports `MATCHED`; rotated identity reports `MISMATCH`; `vc_confirm_*` with
nothing pending returns an error.
**`windows-client` CMake preset** — Release, `VOICECAT_BUILD_SHARED=ON`, static MinGW runtime
(`-static-libgcc -static-libstdc++ -static -lwinpthread`), no tools/tests. Outputs
`build/windows-client/bin/voicecat.dll` with zero MinGW DLL dependencies (only Windows system
DLLs remain — verified via `objdump -p`).
**C# solution** (`clients/windows/`, .NET 10 LTS `net10.0-windows`):
- `VoiceCat.Interop` — `[LibraryImport]` P/Invoke surface, `[UnmanagedCallersOnly]` callbacks,
`System.Threading.Channels.Channel<VoiceCatEvent>` event delivery drained by 30ms WinForms
Timer; `VoiceCatClientHandle : SafeHandle` guarantees `vc_client_destroy`.
- `VoiceCat.App` — WinForms UI:
- `ConnectDialog` — saved-server `ListBox`, Add/Remove/Edit; servers persisted to
`%AppData%\VoiceCat\servers.json`; passwords DPAPI-encrypted (`ProtectedData`, opt-in).
- `ServerIdentityDialog` — shown only on `FIRST_CONNECT`/`MISMATCH` (never `MATCHED`);
mismatch text and button ordering are starkly different ("WARNING" framing, Cancel default).
- `MainForm` — `TreeView` channel tree, `ListBox` user list, `RichTextBox` chat, scope
`ComboBox` (Channel/Private), activity-log `ListBox`, voice panel with mic toggle,
mute/deafen checkboxes, VAD/PTT/Always-On radio group, VAD sensitivity `TrackBar`
(1100, hidden for non-VAD modes), device `ComboBox` + refresh, level `ProgressBar`.
- `PerUserTuningDialog` — real-time gain `TrackBar` + mute/NR checkboxes; applied to all
of a user's streams immediately (no OK/Cancel round-trip).
- `PttKeyCaptureDialog` — focus-scoped PTT key capture.
- `VoiceCat.Interop.Tests` — xunit smoke test: connect → TOFU → guest auth → list channels
purely via P/Invoke against a live `voicecat-server.exe`.
**Explicitly out of scope for this pass:**
- macOS/iOS Swift client — pending.
- Admin/moderation UI (kick/ban/permissions/account provisioning) — server-side dispatch for
these messages is M5's job; building the UI now would require building the server side too.
- PTT hotkey is **focus-scoped only** (works while VoiceCat window has focus). A system-wide
`WH_KEYBOARD_LL` hook would require escalated permissions and risk AV flagging — documented
limitation, not silently omitted.
- Receive-side noise reduction (`vc_set_remote_stream(..., noise_reduction)`) is end-to-end
plumbed but behaviorally a passthrough no-op (`ApmPassthrough`, no PCM modification) —
same as before M4. The per-user NR checkbox in `PerUserTuningDialog` is labeled accordingly.
---
## Decisions log ## Decisions log
All architecture/scope decisions are settled and recorded in All architecture/scope decisions are settled and recorded in

View File

@@ -10,7 +10,7 @@ stable **C ABI** (`voicecat.h`).
┌───────────────────────────────────────────┐ ┌───────────────────────────────────────────┐
macOS / iOS (Swift) │ │ Windows (C#) macOS / iOS (Swift) │ │ Windows (C#)
┌──────────────────┐ │ libvoicecat (C++) │ ┌──────────────────┐ ┌──────────────────┐ │ libvoicecat (C++) │ ┌──────────────────┐
│ SwiftUI views │ │ ┌─────────────────────────────────────┐ │ │ WinUI/Avalonia │ SwiftUI views │ │ ┌─────────────────────────────────────┐ │ │ WinForms (.NET 10
│ AVAudioSession │──┼─▶│ C ABI (voicecat.h) │◀─┼──│ LibraryImport │ │ AVAudioSession │──┼─▶│ C ABI (voicecat.h) │◀─┼──│ LibraryImport │
│ Swift↔C++ interop│ │ ├─────────────────────────────────────┤ │ │ P/Invoke │ │ Swift↔C++ interop│ │ ├─────────────────────────────────────┤ │ │ P/Invoke │
└──────────────────┘ │ │ Session / Protocol state machine │ │ └──────────────────┘ └──────────────────┘ │ │ 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 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 (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. **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. - **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 `[UnmanagedCallersOnly]` static methods for `on_event`/`on_level` to avoid delegate-lifetime
delegate lifetime pitfalls. UI in **WinUI 3** (most native) or **Avalonia** (if we later pitfalls. UI in **WinForms (.NET 10)** — chosen over WinUI 3/Avalonia for its mature,
want a single C# UI across desktop OSes). 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 ## 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. - **Exit:** a user shares mic + desktop audio; listeners control each independently.
### M4 — Native clients ### M4 — Native clients
- **Windows (C#/WinUI):** connect, saved-server list, channel tree, voice, text, device
pickers, meters, VAD/PTT + per-user NR controls. **Windows (C#/WinForms, .NET 10) ✓ complete 2026-06-17:**
- **macOS (Swift/SwiftUI):** same. - Connect, saved-server list (JSON, DPAPI-encrypted passwords), TOFU identity dialog.
- **iOS (Swift):** AVAudioSession integration, mic permission, foreground voice; ReplayKit - Channel tree (`TreeView`), user list, join (incl. password-protected channels).
broadcast extension for `SCREEN_AUDIO`. - Voice: mic start/stop, mute/deafen, VAD/PTT/**always-on** mode, **VAD sensitivity slider**
- In-app **admin interface** (account provisioning, bans) for admin users. (live threshold update via `vc_set_vad_threshold`), device picker, level meter.
- **Exit:** non-technical user installs a client, saves a server, and joins. - 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 ### M5 — Moderation, polish, and beyond
- Permissions/roles, kick/ban/server-mute, channel passwords UI. - 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 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 `webrtc-audio-processing` stays a tracked future swap (e.g. if/when a Linux build target
exists). (voice.md §8, §11) 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 ## 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 Self-hosting means most servers won't have a CA-signed cert for a hostname. We support
both, advertised in `ServerHello`: both, advertised in `ServerHello`:
1. **TOFU (Trust On First Use)** — default for hobby servers. The server has a long-lived 1. **TOFU (Trust On First Use)** — default for hobby servers. On first connect the client
**Ed25519 identity key**; its fingerprint is shown to the user on first connect (like shows an identity dialog and, if accepted, pins the value locally. Subsequent connects
SSH host keys / TeamSpeak server keys) and pinned locally. Subsequent connects verify verify the pin silently; a changed value warns loudly (`MISMATCH`).
the pin; a changed key warns loudly. The TLS cert is self-signed and bound to this
identity key.
2. **PKI** — a server with a domain can use a normal CA-signed cert (e.g. Let's Encrypt); 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. 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 Client certificates are reserved for a future "key-based identity" option (see roadmap) but
are not required in v1. 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). | | 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. | | 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 | | Concern | Choice | Notes |
|---------|--------|-------| |---------|--------|-------|
| Runtime | **.NET 8+** | LTS. | | Runtime | **.NET 10 LTS** (`net10.0-windows`) | In-service until 2028. |
| 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. | | 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. |
| 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. | | 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. |
| Audio | handled by the core (miniaudio/WASAPI) | C# only drives device selection + meters. | | 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`) ## 3. Server (`voicecat-server`)