feat(M4): Windows WinForms client, TOFU identity pinning, VAD threshold + always-on mode
Core ABI extensions (voicecat.h): - vc_list_channels / vc_list_users / vc_list_user_streams — pull-based snapshot getters for the channel-tree and user-list UI; session_model_mu_ guards cross-thread reads - VC_EVENT_JOIN_RESULT / vc_join_channel — channel join with optional password - VC_EVENT_SERVER_IDENTITY + vc_confirm_server_identity — TOFU gate that blocks io_thread_ until the UI approves or rejects; pins TLS leaf-cert SHA-256 (not declared Ed25519) - vc_get_server_identity_display — Ed25519 fingerprint for human-readable display only - VC_INPUT_ALWAYS_ON = 2 in vc_input_mode — transmit unconditionally, no VAD gate - vc_set_vad_threshold — live RMS threshold update (0.0–1.0); EnergyVadProcessor stores it atomically so the audio RT path reads without a lock C++ implementation: - SessionModel::apply_snapshot / apply_channel_event fixed to populate parent_id, password_protected, and max_users (were permanently zeroed) - TlsContext::peer_cert_fingerprint — SHA-256 of peer leaf cert DER via mbedTLS - TofuStore split into peek (read-only) + pin (write) so first-connect only persists after user approval; tofu_store_path in vc_config for per-user pin file location - TcpAcceptor uses dual-stack IPv6+IPv4 fallback (fixes localhost → ::1 on Windows) - windows-client CMake preset: Release shared DLL, static MinGW runtime, no tools/tests - New C++ tests: test_channel_user_list_abi, test_tofu_flow (14/14 green) Windows client (clients/windows/ — .NET 10 WinForms): - VoiceCat.Interop: LibraryImport P/Invoke surface, UnmanagedCallersOnly callbacks, Channel<VoiceCatEvent> event delivery drained by 30ms WinForms Timer - VoiceCat.App: ConnectDialog (saved servers, DPAPI password storage), ServerIdentity- Dialog (TOFU first-connect / mismatch warning), MainForm (channel TreeView, user ListBox, RichTextBox chat, voice controls, device pickers, VAD/PTT/always-on mode, per-user gain/mute/NR tuning, VAD sensitivity TrackBar, level meter ProgressBar) - PttKeyCaptureDialog — focus-scoped PTT key capture (documented limitation) - PerUserTuningDialog — real-time gain/mute/NR applied to all of a user's streams - Accessibility: explicit AccessibleName/Description on every control, & mnemonics, Activity log ListBox as durable screen-reader record, AutomationNotification for curated live announcements Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
#include "audio/audio_engine.h"
|
||||
#include "codec/opus_codec.h"
|
||||
#include "crypto/crypto.h"
|
||||
#include "crypto/tofu_store.h"
|
||||
#include "net/voice_frame.h"
|
||||
#include "protocol/envelope.h"
|
||||
#include "protocol/protocol.h"
|
||||
@@ -49,6 +50,7 @@ struct vc_client {
|
||||
vc_result stream_stop(uint32_t stream_id);
|
||||
vc_result set_input_device(uint32_t stream_id, const char* device_id);
|
||||
vc_result set_input_mode(vc_input_mode mode);
|
||||
vc_result set_vad_threshold(float threshold);
|
||||
vc_result set_push_to_talk(bool active);
|
||||
vc_result set_self_mute(bool mic_muted, bool deafened);
|
||||
vc_result set_remote_stream(uint32_t user_id, uint32_t stream_id, float gain, bool muted,
|
||||
@@ -58,6 +60,15 @@ struct vc_client {
|
||||
|
||||
vc_result list_devices(vc_device_kind kind, vc_device_list* out);
|
||||
|
||||
// M4: channel/user/stream snapshot getters (read session_model_; see voicecat.h).
|
||||
vc_result list_channels(vc_channel_list* out);
|
||||
vc_result list_users(vc_user_list* out);
|
||||
vc_result list_user_streams(uint32_t user_id, vc_stream_summary_list* out);
|
||||
|
||||
// M4: TOFU server-identity gate (see voicecat.h's VC_EVENT_SERVER_IDENTITY doc comment).
|
||||
vc_result confirm_server_identity(bool accept);
|
||||
vc_result get_server_identity_display(char* out_buf, size_t buf_cap, size_t* out_len);
|
||||
|
||||
// M3: effective Opus config for a (user_id, stream_id) — our own pending/active local
|
||||
// streams, or any peer's broadcast StreamInfo.audio.
|
||||
vc_result get_stream_audio_config(uint32_t user_id, uint32_t stream_id,
|
||||
@@ -113,8 +124,19 @@ struct vc_client {
|
||||
uint64_t server_session_id_{0};
|
||||
std::atomic<uint64_t> next_req_id_{1};
|
||||
|
||||
// Client-side session model
|
||||
// Client-side session model. Mutated only on io_thread_ (handle_server_state/
|
||||
// handle_user_event/handle_channel_event), but read from any thread via the M4
|
||||
// list_channels/list_users/list_user_streams getters — session_model_mu_ guards both.
|
||||
voicecat::session::SessionModel session_model_;
|
||||
mutable std::mutex session_model_mu_;
|
||||
|
||||
// ── M4: TOFU server-identity gate ───────────────────────────────────────────
|
||||
std::unique_ptr<voicecat::crypto::TofuStore> tofu_store_; // owns the pin file
|
||||
std::mutex tofu_mu_;
|
||||
std::condition_variable tofu_cv_;
|
||||
bool tofu_decision_pending_{false};
|
||||
bool tofu_accept_{false};
|
||||
std::string pending_identity_fp_hex_; // ServerHello's Ed25519 fp, display-only
|
||||
|
||||
// ── M2: UDP / media plane ────────────────────────────────────────────────────
|
||||
std::array<uint8_t, 16> udp_token_{};
|
||||
@@ -187,6 +209,7 @@ struct vc_client {
|
||||
// lands (handle_stream_announce_result, on io_thread_ — not the RT capture callback).
|
||||
std::atomic<vc_input_mode> current_input_mode_{VC_INPUT_VOICE_ACTIVATION};
|
||||
std::atomic<bool> ptt_active_{false};
|
||||
std::atomic<float> vad_threshold_{0.025f}; // remembered across mode switches
|
||||
std::unique_ptr<voicecat::audio::ApmProcessor> mic_vad_;
|
||||
|
||||
// teardown_voice() is called both from run_io()'s own cleanup (on the io_thread_, when
|
||||
@@ -206,6 +229,8 @@ struct vc_client {
|
||||
void handle_auth_result(const voicecat::v1::AuthResult& msg);
|
||||
void handle_server_state(const voicecat::v1::ServerStateSnapshot& snap);
|
||||
void handle_user_event(const voicecat::v1::UserEvent& ue);
|
||||
void handle_channel_event(const voicecat::v1::ChannelEvent& ce);
|
||||
void handle_join_channel_result(const voicecat::v1::JoinChannelResult& msg);
|
||||
void handle_text_message(const voicecat::v1::TextMessage& msg);
|
||||
void handle_disconnect(const voicecat::v1::Disconnect& msg);
|
||||
void handle_udp_binding_ack(const voicecat::v1::UdpBinding& msg);
|
||||
|
||||
Reference in New Issue
Block a user