Add encrypted UDP voice (OCB2-AES128) with TCP fallback

- Port of Mumble's CryptStateOCB2 (XEX* counter-measures, late/lost/replay
  handling, nonce resync), verified against Mumble's OCB2 test vectors
- UDP channel per connection in the main process, to the address the TLS
  connection reached; used only while the server answers UDP pings, falls back
  to the TCP tunnel automatically; "voice over TCP only" setting
- Voice statistics show the live transport
- Information dialog no longer infers the transport from ping counters
- Message box: no padding, input fills the bar; Edit HTML only in descriptions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 01:14:36 +02:00
co-authored by Claude Opus 5.5
parent 9a3aeb29ad
commit b44b8230d4
19 changed files with 616 additions and 48 deletions
+3 -1
View File
@@ -21,6 +21,7 @@ export interface VoiceSettings {
bitrate: number; // Opus bits per second
frameMs: 10 | 20 | 40 | 60;
jitterMs: number;
forceTcp: boolean; // send voice through the TCP connection even when UDP works
// Per-user local volume and mute, keyed by certificate hash (or name without one)
userVolumes: Record<string, number>;
localMutes: Record<string, boolean>;
@@ -41,6 +42,7 @@ const defaults: VoiceSettings = {
bitrate: 40000,
frameMs: 20,
jitterMs: 60,
forceTcp: false,
userVolumes: {},
localMutes: {}
};
@@ -337,7 +339,7 @@ class VoiceEngine {
private sendPacket(opus: Uint8Array, last: boolean): void {
const client = this.client;
if (!client) return;
client.sendVoiceTunnel(encodeVoice({ target: 0, frame: this.frameCounter, opus, last }, client.protobufVoice));
client.sendVoice(encodeVoice({ target: 0, frame: this.frameCounter, opus, last }, client.protobufVoice));
this.frameCounter += this.settings.frameMs / 10;
this.stats.sent++;
}