Retire legacy sources and verify managed iOS deployment
.NET port / test (macos-latest) (push) Canceled after 0s
.NET port / test (ubuntu-24.04) (push) Canceled after 0s
.NET port / test (windows-latest) (push) Canceled after 0s
.NET port / apple-client (push) Canceled after 0s

This commit is contained in:
2026-09-19 22:40:48 +02:00
parent 42e3bbe14c
commit c9ed832459
109 changed files with 877 additions and 4981 deletions
+31 -101
View File
@@ -1,113 +1,43 @@
# Tech Stack & Dependencies
# Technology stack
## Initial .NET rewrite
## Managed runtime
The parallel rewrite under `dotnet/` targets .NET 10. Its initial dependencies are
Google.Protobuf 3.36.1 (BSD-3-Clause), build-only Grpc.Tools 2.83.0 (Apache-2.0), and
BouncyCastle.Cryptography 2.6.2 (MIT). Media AEAD prefers the platform implementation;
BouncyCastle provides the managed fallback and is the planned TLS/exporter provider.
No managed server or audio replacement is shipped yet.
VoiceCat targets .NET 10. Shared libraries use plain `net10.0`; WinForms, AppKit, and UIKit are
leaf platform projects.
Project files and NuGet lock files pin versions. `dotnet/check-licenses.ps1` checks
all restored direct/transitive packages against a permissive license allowlist in CI;
unknown or copyleft licenses fail. See `dotnet/README.md` for build and test commands.
The existing implementation's dependency choices follow below.
| Area | Technology | Notes |
|---|---|---|
| Control schema | Protocol Buffers / Google.Protobuf | `proto/voicecat.proto` is authoritative |
| TLS 1.3/exporters | BouncyCastle.Cryptography | Managed TLS is required for exporter-derived media keys |
| Media AEAD | System.Security.Cryptography ChaCha20Poly1305 | Managed fallback is behavior-tested |
| Passwords | Konscious Argon2id | Bounded parameters, PHC strings |
| Persistence | Microsoft.Data.Sqlite | Server-local SQLite with explicit migrations |
| Tests | xUnit | Unit, socket integration, allocation, CLI, and production-package checks |
Concrete library choices with versions and rationale. Everything in the **core** is C++
(C++20). UIs are Swift and C#. Build is CMake + vcpkg.
NuGet packages and their lock files are committed. `dotnet/check-licenses.ps1` enforces the
permissive-license policy.
## 1. Core library (`libvoicecat`, C++20)
## Native media
| Concern | Choice | Version (as of 2026-06) | Why / notes |
|---------|--------|-------------------------|-------------|
| Sockets, timers, async | **Standalone Asio** | 1.30.x | Header-only, no Boost dependency, cross-platform TCP+UDP+timers, one reactor for client and server. (Boost.Asio is interchangeable if we already pull Boost.) |
| TLS 1.3 (control) | **mbedTLS 3.6 LTS** | 3.6.x (LTS ≥ Mar 2027) | **Apache-2.0** (permissive — clean for eventual closed-source distribution). TLS 1.3 client+server, plus `mbedtls_ssl_export_keying_material()` to seed the media AEAD. **Static-links cleanly → single self-host binary.** OpenSSL 3.x (Apache-2.0) is an interchangeable alternative. No DTLS/wolfSSL (GPL) — see [security.md](security.md) §2. |
| Crypto primitives + password hashing + media AEAD | **libsodium** | 1.0.20 | **ISC.** Argon2id (`crypto_pwhash`), ChaCha20-Poly1305 (per-frame media encryption), Ed25519 server identity, X25519, CSPRNG. Audited, hard to misuse. |
| Audio codec | **libopus** | **1.6** (2025-12) | Per-channel mono/stereo, bitrate, frame size; in-band FEC, DTX, PLC, and optional **DRED** deep redundancy; Opus HD/96 kHz available. The whole reason the design is codec-flexible. |
| Audio capture/playback | **miniaudio** | 0.11.x | Single-header, public-domain, backends for **WASAPI / CoreAudio / ALSA / PulseAudio**. One real-time abstraction across all desktop targets; keeps the RT path identical. |
| Audio DSP — noise suppression (NS) | **RNNoise** (vendored, `third_party/rnnoise/`) | xiph @ `70f1d25` (2026-06) | **BSD-3-Clause + CC0-1.0** (model). Hybrid DSP/RNN speech denoiser, mono/48 kHz, ~60× real time, no deps. The shipped NS backend behind `ApmProcessor` (`RnnoiseProcessor`), used by both send-side mic NR (`vc_set_input_noise_reduction`) and per-listener receive NR (`vc_set_remote_stream`). Vendored (not vcpkg) because the vcpkg port is `!windows !arm`. See [voice.md](voice.md) §10. |
| Audio DSP — AEC/AGC/VAD | **webrtc-audio-processing** (APM) — **planned, not built** | 1.x (standalone APM) | **BSD-3**, but has no working Windows/MSVC build upstream (GCC-only Meson, MinGW support unfinished, hard `abseil-cpp` dep — see roadmap.md §2). v1 ships a lightweight, dependency-free energy/RMS VAD (`EnergyVadProcessor`, `core/src/audio/apm_processor.cpp`); **NS now exists via RNNoise (row above)**, but there is still **no AEC or AGC** (iOS gets AEC/NS/AGC natively from VPIO). Real APM stays a tracked future swap behind the same `ApmProcessor` interface. |
| Resampling + jitter ref | **speexdsp** | 1.2.x | BSD. Resampler for non-48 kHz devices; lightweight jitter-buffer reference. (No longer the NS/AGC/VAD source — APM replaces it.) |
| Control serialization | **Protocol Buffers** (protobuf-lite) | 5.x (proto3) | Codegen for C++/C#/Swift; additive, forward/backward compatible; `oneof` envelopes. `nanopb` is a fallback if footprint matters. |
| Server persistence | **SQLite** | 3.4x | Accounts, channels, bans, config. Zero-admin, single file, ships everywhere. |
| Logging | **spdlog** | 1.14.x | Fast, async-capable; off the RT path. |
`native/media` builds one narrow library named `voicecat_media`:
Resampling note: Opus runs internally at 48 kHz; miniaudio can deliver 48 kHz directly, so
explicit resampling (speexdsp/libsamplerate) is only needed when a device can't do 48 kHz.
- Opus 1.5.2, checksum-pinned and built with DRED/Deep PLC support.
- RNNoise, vendored under `native/rnnoise` at the repository-pinned source/model revision.
## 2. Clients
The shim contains codec and denoiser entry points only. Managed code owns networking, crypto,
jitter, mixing, state, and lifecycle. CMake is retained solely for this boundary and its Apple
static archives.
### Apple clients — .NET native UI with a Swift ReplayKit exception
## Platform clients
| Concern | Choice | Notes |
|---------|--------|-------|
| Language | **Swift 5.9+** | Direct **Swift↔C interop** — the C ABI (`voicecat.h`) is imported as a Clang module (`import VoiceCatC`) via a module map in the XCFramework headers; no manual struct/function redeclaration (unlike the C# P/Invoke layer). A Swift wrapper (`VoiceCatCore` package) provides Swift-idiomatic types on top. |
| UI — macOS | **AppKit** | Chosen over SwiftUI for the most mature, granular **VoiceOver** accessibility story (per-control `accessibilityLabel`/`accessibilityHelp`/`accessibilityRole`, `NSAccessibility.post(.announcement)` for live announcements) — the same rationale that drove the Windows client to WinForms over WinUI 3 for screen-reader (NVDA/JAWS/Narrator) UIA support (resolved decision in `docs/roadmap.md`). macOS 14 (Sonoma) deployment target. |
| UI — iOS | **C# / UIKit (`net10.0-ios`)** | Native UIKit keeps direct lifecycle/audio control and predictable VoiceOver semantics. MAUI was rejected because a cross-platform abstraction provides no benefit for this platform-specific client. iOS 18.0 deployment target. |
| Shared core | **VoiceCatCore** Swift Package | One Swift library wrapping the C ABI, consumed by both the macOS AppKit app and the iOS SwiftUI app. Mirrors the C# `VoiceCat.Interop` layer. Events delivered on `@MainActor` via a coalesced `DispatchQueue.main` drain (the Swift analog of C#'s `Channel<VoiceCatEvent>` + 30ms WinForms Timer pump). |
| Audio session (iOS) | **AVAudioSession + AVAudioEngine from C#** | The UIKit host owns category, permission, interruptions, routes, VPIO capture and planar playback. Converted PCM crosses bounded managed rings; callbacks allocate no managed objects, lock, or block. |
| Packaging | .NET Apple workloads + Xcode appex | The managed iOS host statically links merged Opus/RNNoise archives. MSBuild invokes Xcode to build/embed the small Swift ReplayKit extension, which communicates through the versioned App Group PCM ring. |
| Future | CallKit / PushKit | For background VoIP + incoming-call UX on iOS. Post-v1. |
- Windows: .NET WinForms for mature UI Automation and screen-reader behavior.
- macOS: .NET AppKit for direct accessibility and Core Audio/ScreenCaptureKit control.
- iOS: .NET UIKit for direct VoiceOver and AVAudioSession lifecycle control.
- iOS broadcast upload: Swift/ReplayKit under `native/apple/broadcast`; it writes PCM to the
frozen App Group ring and deliberately does not host .NET or the protocol stack.
### Windows — C# (shipped in M4, 2026-06-17)
## Licensing
| Concern | Choice | Notes |
|---------|--------|-------|
| 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`)
- Pure C++ linking the core; **no GUI**. Runs on **Linux** (primary), **macOS**, **Windows**.
- Config via a `server.toml` (`allow_guests`, ports, channel defaults, Opus policy, TLS cert
paths or auto-self-signed + Ed25519 identity, Argon2id cost params, rate limits).
- SQLite for state. Single process for v1; interfaces drawn so a multi-node build is
*possible* later but explicitly out of scope.
- Packaging: static-ish binary per OS; systemd unit + Docker image for Linux.
## 4. Build & tooling
| Tool | Use |
|------|-----|
| **CMake** (3.25+) | One build graph for core + server + test CLI; UI projects consume the built core. |
| **vcpkg** (manifest mode) | Pin C/C++ deps (opus, libsodium, mbedtls, protobuf, sqlite3, spdlog, asio, miniaudio — see `vcpkg.json`). `webrtc-audio-processing`/`speexdsp` are **not** in the manifest: no working vcpkg port / no working Windows/MSVC build exists upstream for the former; the latter was never actually wired up (the lightweight VAD needs no resampler). Reproducible across OSes. Triplet auto-resolved from the host platform by [`cmake/voicecat-toolchain.cmake`](../cmake/voicecat-toolchain.cmake) — `x64-mingw-static` on Windows, `x64-linux` on Linux, `arm64-osx` on Apple Silicon. Apple platform scaffolding presets (`apple-dev`/`apple-ios`/`apple-ios-sim`) produce static `libvoicecat.a` slices for XCFramework consumption. vcpkg itself is bundled as a git submodule at `vcpkg/`, pinned to `vcpkg.json`'s `builtin-baseline` commit — `VCPKG_ROOT` overrides it for an external checkout. See [building.md §2](building.md#2-one-time-setup-for-the-real-deps-presets). |
| **protoc** | Generate C++/C#/Swift from `core/proto/*.proto` (single source of truth). |
| **clang-format / clang-tidy** | Style + static analysis on the core. |
| **CTest + a fuzz target** | Unit/integration tests; fuzz the frame parser and protobuf boundary (security-sensitive). |
| **GitHub Actions** (or similar) | Matrix CI: Linux/macOS/Windows core+server; Xcode build for Apple; `dotnet` build for Windows. |
## 5. Licensing — permissive only (hard rule)
The code will eventually be distributed in **closed-source** form, so **no GPL/LGPL
dependencies are permitted.** Every dependency below is BSD / MIT / ISC / Apache-2.0 /
public-domain:
- **mbedTLS** — Apache-2.0 ✅ · **libsodium** — ISC ✅ · **libopus** — BSD ✅ ·
**miniaudio** — public domain / MIT-0 ✅ · **protobuf** — BSD ✅ ·
**SQLite** — public domain ✅ · **Asio** (standalone) — Boost ✅ · **spdlog** — MIT ✅ ·
**RNNoise** — BSD-3-Clause (code) + CC0-1.0 (model) ✅, vendored in `third_party/rnnoise/`
(not vcpkg — the port is `!windows !arm`; see [`third_party/README.md`](../third_party/README.md)).
**webrtc-audio-processing** would be BSD-3 ✅ if/when it's actually built in (see §1) —
not a live dependency today, so not part of the resolved vcpkg graph the license scanner
below checks.
- **Explicitly rejected:** **wolfSSL** (GPLv2/commercial) and any DTLS stack that would drag
in copyleft. The exported-keys + AEAD media design (security.md §2) removes the need for
one entirely.
- CI runs a license scanner over the resolved vcpkg graph and **fails the build on any
GPL/LGPL transitive dependency**, so this rule can't silently regress.
## 6. Why not the obvious alternatives
- **WebRTC** — explicitly rejected: ICE/SDP/TURN complexity, huge dependency, opaque. We
want plain TCP+UDP we fully control.
- **QUIC** — capable (reliable streams + datagrams + TLS 1.3 in one), but heavier and drifts
toward the complexity we're avoiding. Revisit only if NAT traversal/multiplexing pain
appears.
- **gRPC** for control — pulls HTTP/2 and a lot of surface for what is a simple framed
message stream over TLS. Plain protobuf-over-framed-TLS is enough.
- **A Rust core** — viable and memory-safe, but the user prefers C++ and the Swift/C#
binding story is marginally simpler from C++ (Swift can even consume C++ directly).
GPL and LGPL dependencies are forbidden. Current production dependencies are permissively
licensed; RNNoise code is BSD-3-Clause and its model data is CC0. Preserve license notices in
published native artifacts.