Update docs
This commit is contained in:
12
CLAUDE.md
12
CLAUDE.md
@@ -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
|
||||
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
|
||||
> pass closing the device enumeration / VAD-PTT input gate / stereo playback / WASAPI loopback
|
||||
> gaps it left open (`ctest --test-dir build/m1-dev` green — 12/12 tests, including
|
||||
> `test_vad_ptt_devices`, and `vccli --voice`/`--list-devices` manually verified live). Real
|
||||
> `webrtc-audio-processing` (AEC/NS/AGC) is still unbuilt — no working Windows/MSVC port
|
||||
> upstream — so v1 ships a lightweight energy/RMS VAD instead. Next up is **M4** (native
|
||||
> clients). See [`PROGRESS.md`](PROGRESS.md).
|
||||
> **One-line status:** M4 (Windows WinForms C# client) is complete — connect, saved servers,
|
||||
> TOFU identity pinning, channel tree, voice (VAD/PTT/always-on + VAD threshold slider +
|
||||
> per-user tuning), text chat, device pickers, level meters. `ctest --preset m1-dev` green —
|
||||
> 14/14 tests. `dotnet build` green — 0 warnings. macOS/iOS Swift client and M5 (moderation)
|
||||
> are next. See [`PROGRESS.md`](PROGRESS.md).
|
||||
|
||||
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
|
||||
|
||||
83
PROGRESS.md
83
PROGRESS.md
@@ -53,7 +53,14 @@ up instantly. Newest status at the top.
|
||||
(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
|
||||
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 §M4–M5`.
|
||||
|
||||
---
|
||||
|
||||
@@ -63,7 +70,7 @@ up instantly. Newest status at the top.
|
||||
- [x] **M1 — Control plane** ✓ complete (2026-06-15)
|
||||
- [x] **M2 — Voice, single stream** ✓ 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, …)
|
||||
|
||||
---
|
||||
@@ -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`
|
||||
(1–100, 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
|
||||
|
||||
All architecture/scope decisions are settled and recorded in
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user