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
+7
View File
@@ -180,6 +180,13 @@ try {
const statsText = await page.locator('.stats').textContent();
const decoded = Number(/decoded (\d+)/.exec(statsText ?? '')?.[1]);
assert.ok(decoded >= 20, `decoded frames reported: ${statsText}`);
// Voice runs over encrypted UDP once the server answers UDP pings
await page.locator('.stats', { hasText: /Transport: UDP, \d+ ms ping/ }).waitFor({ timeout: 10000 });
await page.getByLabel(/Send voice over TCP only/).check();
await page.locator('.stats', { hasText: 'Transport: TCP tunnel' }).waitFor();
await page.getByLabel(/Send voice over TCP only/).uncheck();
await page.locator('.stats', { hasText: /Transport: UDP/ }).waitFor();
console.log('ok: voice transport UDP, with a TCP-only switch');
await page.getByRole('button', { name: 'Done', exact: true }).click();
console.log(`ok: ${decoded} frames decoded`);
await setMode('Push to talk');