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
+10
View File
@@ -161,6 +161,14 @@ export class Session {
this.expanded = all;
}
// How voice travels right now
get voiceTransport(): { udp: boolean; rtt: number } {
void this.tick;
const c = this.client;
// The setting (reactive) rather than the client's copy, so toggling it updates the view
return { udp: !!c && c.udpOk && !voice.settings.forceTcp, rtt: c?.udpRtt ?? 0 };
}
// true / false when known, null when the server has not told us yet
can(channelId: number, bit: number): boolean | null {
void this.tick;
@@ -236,6 +244,7 @@ export class Session {
}
const client = new MumbleClient(codec);
client.forceTcp = voice.settings.forceTcp;
this.client = client;
this.wire(client);
// TLS is up but the server may still never finish the login
@@ -309,6 +318,7 @@ export class Session {
bump();
});
client.on('ping', rtt => { if (live()) this.rtt = rtt; });
client.on('udp', bump);
client.on('user', (user, changed, actor, isNew) => {
if (!live()) return;