Commit Graph
231 Commits
Author SHA1 Message Date
Brandon McGinty (deepseek) e93087afeb Fix udp15Server decrypt IV: use server_nonce, not client_nonce
SetUDP15Crypto was initializing udp15Server with setup15(key,
serverNonce, clientNonce), which set decryptIV=clientNonce.
But the server encrypts with server_nonce, so the client-side
decryptIV must be server_nonce.

The incoming IV byte from the server was 0x58, but our decryptIV[0]
was 0xab (client_nonce[0]). Diff of -83, way outside the ±30
acceptance window.

Now udp15Server.decryptIV is directly set to server_nonce.
2026-08-09 02:30:28 -04:00
Brandon McGinty (deepseek) 4c7731043c Add missing 0x00 type byte to outbound MumbleUDP.Audio packets
Wumble prepends Bytes[0_u8] before the protobuf fields when building
the plaintext for encryption. My encodeUDPAudio was missing this type
byte, so the server decrypted our packets and saw raw protobuf without
a message type indicator — silently dropping them.

Also added type-byte dispatch on receive side: 0x00 = audio, 0x01 = ping.
2026-08-09 02:25:29 -04:00
Brandon McGinty (deepseek) 266f1fce73 Fix IV increment direction and OCB decrypt checksum
Two critical bugs found by testing against wumble's Crystal OCB:

1. IV increment was big-endian (byte[15] first). Wumble uses
   little-endian (byte[0] first). Since byte[0] is what goes on
   the wire, it never changed — server saw every packet as a
   replay. Changed to little-endian increment matching wumble's
   increment_encrypt_iv.

2. In ocb15Decrypt's final partial block, the checksum was
   computed from recovered plaintext XOR pad (= ciphertext).
   But ocb15Encrypt computes it from ciphertext XOR pad
   (= plaintext). Now both use ciphertext XOR pad.

Added udp15_test.go with:
- TestIncrementIV: verifies little-endian overflow behavior
- TestOCB15RoundTrip: encrypt→decrypt for 0-100 byte inputs
- TestCryptState15RoundTrip: full pipeline with real crypto keys
- TestUDPAudioProtobuf: protobuf encode/decode round-trip
2026-08-09 01:11:56 -04:00
Brandon McGinty (deepseek) 63941f7785 Fix double tone-generator start in tone-test mode
connect() starts the tone generator but didn't set b.Tx=true,
so setTransmit would start a second instance. Now b.Tx is set
so the UI toggle works as a stop/restart instead of double-start.
2026-08-09 01:02:53 -04:00
Brandon McGinty (deepseek) 5b9b3ad421 Fix nil udpConn race in WriteAudioUDP15, reduce log noise
- Snapshot c.udpConn to local variable to avoid race with disconnect
  cleanup goroutine nil'ing it between the nil check and Write call.
- Move per-packet hex dumps from Info to Debug level.
- Only hex-dump the first received packet at Info level.
2026-08-09 00:58:50 -04:00
Brandon McGinty (deepseek) 50987d412c Switch UDP to Mumble 1.5 native protocol (matching wumble)
The old gumble library uses the legacy UDPVoice format:
  [plaintext header: type|target, session, seq] [OCB2 encrypted payload]

Modern Murmur 1.5+ servers speak the native UDP protocol:
  [iv_byte(1)] [tag(3)] [OCB encrypted MumbleUDP.Audio protobuf]

This is why zero incoming UDP packets were seen — the server
silently dropped all legacy-format datagrams.

Changes:
- New udp15.go: MumbleUDP.Audio protobuf encode/decode, OCB
  encrypt/decrypt matching wumble's implementation, cryptState15
  with IV-increment nonce and replay-protection history
- udp.go: ping uses 1.5 native format (type 0x01, encrypted
  protobuf), reader delegates to HandleUDPPacket15
- client.go: WriteAudio tries WriteAudioUDP15 first
- crypt.go: SetUDP15Crypto called from handleCryptSetup

OCB variant matches wumble's crypt_state.cr exactly: full 16-byte
nonce (incremented IV), no associated data, final block padding
with byte[15] = remaining*8.
2026-08-09 00:51:21 -04:00
Brandon McGinty (deepseek) e1468cd204 Add verbose packet-level logging throughout UDP audio pipeline
Logs every step of UDP send/receive:

SEND path (WriteAudioUDP):
  - counter, nonce, plaintext len, opus len, final flag
  - full packet hex dump after encryption

RECEIVE path (udpReadRoutine -> handleUDPPacket -> handleUDPTunnel):
  - raw packet hex on arrival
  - type byte, audio type, target parsed
  - session ID, sequence number, header/encrypted lengths
  - user lookup result
  - cryptIn state, counter, nonce for decryption
  - encrypted payload hex
  - decrypt success/failure with error detail
  - plaintext hex after decryption
  - dispatch to handleUDPTunnel

UDPTUNNEL path (handleUDPTunnel):
  - session, seq, audio length, final flag, buffer remaining
  - Opus decode success/failure
  - pcm sample count after decode

CRYPTO SETUP (cryptState.setup):
  - key, IV, encrypted IV, computed nonce prefix

All at log.Info level so they show up with -log=info (no --debug needed).
2026-08-09 00:36:31 -04:00
Brandon McGinty (deepseek) 819ed6931d Add -tcp flag to disable UDP and force audio through TCP tunnel
Adds Config.DisableUDP field. When set:
- DialWithDialer skips startUDP() entirely (no UDP socket, no UDP reader)
- WriteAudio skips WriteAudioUDP and goes straight to TCP-tunneled audio

Useful for debugging crypto/transport issues — rules out UDP-specific
problems like NAT, firewall, or OCB2 counter mismatches.
2026-08-09 00:30:27 -04:00
Brandon McGinty (deepseek) 8967fdff1a Fix nil b.Stream dereference in tone-test mode
setTransmit, OnMicVolumeUp, OnMicVolumeDown, and the file-playback
auto-transmit path all dereferenced b.Stream without checking for nil.
In tone-test mode b.Stream is never set (OpenAL is skipped), so guard
all call sites.
2026-08-09 00:24:44 -04:00
Brandon McGinty (deepseek) 29be821155 Add --tone-test mode: 440 Hz Opus tone + incoming audio capture
Adds a --tone-test flag that bypasses all OpenAL/soundcard
initialization and instead:

- Generates a 440 Hz sine wave at 48kHz mono, encodes it via Opus,
  and sends it to the Mumble server.

- Saves all incoming decoded audio to a raw PCM file (s16le,
  stereo, 48kHz) specified by --tone-out (default: incoming.pcm).

Useful for end-to-end testing of the Opus encode/decode pipeline
and Mumble transport without requiring physical audio hardware.

Usage:
  barnard --server HOST --tone-test --tone-out /tmp/in.pcm

  ffplay -f s16le -ar 48000 -ac 2 incoming.pcm
2026-08-09 00:19:41 -04:00
Brandon McGinty (deepseek) b2a1d2f846 fix: add OpenAL device open logging and nil pointer safety
Add info-level logging showing which device OpenAL opens and with what
format. Also add nil checks for inputDevice/outputDevice pointers to
prevent panics when config fields are unset.
2026-08-08 23:45:55 -04:00
Brandon McGinty (deepseek) a24fca382d chore: update config for vsink virtual mic 2026-08-08 23:30:08 -04:00
Brandon McGinty (deepseek) 41500f2c54 chore: add example config for bmc-beta test bot 2026-08-08 23:01:35 -04:00
Brandon McGinty (deepseek) b861193306 feat: add -auto-transmit flag to start transmitting on connect
When -auto-transmit is passed, barnard automatically keys up the
microphone as soon as the connection sync completes. Useful for
testing, bots, and unattended operation.

Usage:
  barnard -auto-transmit -server=mumble.example.com -username=test_bot
2026-08-08 22:52:27 -04:00
Brandon McGinty (deepseek) 3ca57a847c fix: improve CryptSetup and UDP receive logging
- Suppress 'incomplete fields' log spam after crypto is initialized
  (server sends partial CryptSetup for key rotation)
- Log 'CryptSetup updated' at debug when keys are refreshed
- Add warning log when UDP decryption fails (OCB auth error)
- Add debug log for UDP packets from unknown sessions
- Add debug log for UDP packets before crypto is ready
2026-08-08 22:50:12 -04:00
Brandon McGinty (deepseek) 5984989ea7 fix: skip TCP-tunneled audio when UDP is active to prevent double-processing
When UDP audio transport is active, ignore UDPTunnel packets (type 1)
on the TCP connection. Without this, audio packets arrive via both
TCP and UDP, causing the per-user sequence tracker to advance by 2
per frame. The gap detection then generates spurious PLC frames for
every single audio packet, producing log spam and audio artifacts.

The readRoutine now checks c.udpActive before dispatching packet
type 1.
2026-08-08 22:42:18 -04:00
Brandon McGinty (deepseek) 0ecdbf988f fix: start UDP socket before connection handshake to avoid race
CryptSetup can arrive during the initial sync (before handleServerSync
completes). Previously startUDP() was called after the sync finished,
so if CryptSetup arrived first, crypto was initialized but the UDP
socket didn't exist yet — causing a permanent fallback to TCP.

Move startUDP() to immediately after writing the Version/Authenticate
packets and before waiting for the sync to complete. The UDP socket
is now ready when CryptSetup arrives.
2026-08-08 22:37:53 -04:00
Brandon McGinty (deepseek) 53918788f7 feat: add -logfile flag to write logs to a file
The -logfile flag writes log output to a file in addition to stderr,
so users can run barnard in the TUI without log spam on screen.

Usage:
  barnard -log=debug -logfile=/tmp/barnard.log -server=mumble.example.com
2026-08-08 22:33:23 -04:00
Brandon McGinty (deepseek) e483512f74 feat: add structured logging throughout audio pipeline
Add a lightweight log package (log/log.go) with debug/info/warn/error
levels. Wire it up via a -log flag (default: warn). Logging covers:

UDP transport:
- Socket open/failure, CryptSetup receipt, crypto initialization
- First UDP audio send confirmation
- UDP read errors and packet counts
- TCP fallback reason (no socket, waiting for crypto)

Audio pipeline:
- Source (mic) routine start with config details
- Audio stream start/end per remote user
- First audio stream creation for each user
- Packet loss detection with sequence gaps and PLC generation
- Decoder reset on sequence reordering

Usage: barnard -log=debug -server=mumble.example.com
Levels: debug, info, warn, error
2026-08-08 22:19:42 -04:00
Brandon McGinty (deepseek) a218493eb0 fix: correct OCB implementation to OCB2 (Mumble-compatible)
Rewrite the OCB implementation from scratch using the OCB2 algorithm
that Mumble uses (not RFC 7253 OCB3). Key fixes:
- L_* base value is now E_K(0^128), not the initial offset
- Proper bottom-bit clearing for non-15-byte nonces
- Correct GF(2^128) doubling for offset updates
- Fixed ntz(0) infinite loop
- Fixed AD offset processing to use L_ntz scheme

Add comprehensive tests:
- Round-trip encrypt/decrypt for various sizes, with/without AD
- Tag verification (tampered data, wrong key, wrong nonce)
- Deterministic output verification
- Initial offset computation against known values
- cryptState setup, encrypt/decrypt, counter-based nonce derivation
- Nonce byte-aligned processing for all valid OCB nonce lengths (1-15)
- Big-endian counter endianness verification
- Benchmarks for encrypt and decrypt

pycryptodome comparison tests removed: pycryptodome 3.23.0 does not
match RFC 7253 test vectors and implements a different OCB variant.
OpenSSL CLI doesn't support AEAD ciphers.
2026-08-08 21:53:22 -04:00
Brandon McGinty (deepseek) 6a6f94a11d feat: add UDP audio transport with OCB-AES128 encryption
Add native UDP support for audio transport, which provides much lower
latency than tunneling audio over TCP. The implementation:

- Adds OCB-AES128 encryption (RFC 7253) for UDP audio packets, as
  required by the Mumble protocol.
- Implements the CryptSetup handler to receive encryption keys and
  nonces from the server.
- Opens a UDP socket to the server after TCP connection and starts
  reading encrypted audio once the CryptSetup is received.
- Modifies WriteAudio to try UDP first with automatic fallback to
  the existing TCP tunnel when UDP is unavailable or encryption
  is not set up.
- Adds UDP ping packets to keep NAT bindings alive.
- Cleans up the UDP connection on disconnect.

The OCB implementation is a self-contained pure-Go implementation
using only crypto/aes and crypto/cipher from the standard library.
2026-08-08 21:40:09 -04:00
Brandon McGinty (deepseek) 3e98e93cfb fix: move reclaim() outside processAudioPacket to fix buffer corruption
processAudioPacket accepted reclaim as a closure that captured the
outer goroutine's emptyBufs variable. When reclaim() appended buffers
inside the function, it mutated the outer variable, but the function
also returned its own copy. The caller then overwrote the outer
variable with the returned copy, losing the buffers reclaimed by
reclaim(). This caused OpenAL buffer starvation and audio dropouts.

Move reclaim() calls to the main loop before processAudioPacket, and
remove the reclaim parameter from the function. The caller now always
owns the emptyBufs lifecycle.
2026-08-08 21:19:26 -04:00
Brandon McGinty (deepseek) 0858cd3542 fix: add jitter buffer, sequence tracking, and mic error reporting
Add a jitter buffer in OnAudioStream that collects incoming audio packets,
sorts by sequence number, and releases them in order after a small initial
delay (3 packets, ~30ms). This prevents out-of-order playback from network
jitter and reordered UDP packets.

Add a Sequence field to AudioPacket so the jitter buffer can reorder by
sequence number. PLC frames generated by dispatchPLC now also carry the
correct sequence number so they integrate with the jitter buffer.

Add microphone capture error reporting: when CaptureSamples returns fewer
bytes than expected, report the failure to the user via a callback. The
error is reported once when the failure starts and again when the mic
recovers. Wire this up in client.go to display status messages.
2026-08-08 21:02:33 -04:00
Brandon McGinty (deepseek) 9dd0137975 fix: add synchronization to User audio fields to prevent data races
User.AudioSource, Boost, Volume, and LocallyMuted were accessed from
both the OnAudioStream audio goroutine and the UI goroutine without
synchronization, a data race under the Go memory model.

Replace direct field access with thread-safe getter/setter methods
protected by a per-user mutex:
- SetAudioSource/GetAudioSource for the OpenAL source pointer
- SetBoost/Boost for the audio boost multiplier
- SetVolume/Volume for the volume level
- SetLocallyMuted/LocallyMuted for the local mute state

Update all call sites across config/, barnard.go, client.go,
ui_tree.go, and stream.go.
2026-08-08 20:58:53 -04:00
Brandon McGinty (deepseek) 1bdd7ac52e fix: fix data race on micVolume field
micVolume was accessed from both the UI goroutine (SetMicVolume,
GetMicVolume) and the audio source goroutine (sourceRoutine) without
synchronization, a data race under the Go memory model.

Switch to atomic.Uint32 storing float32 bits to make reads and writes
safe across goroutines.
2026-08-08 19:59:07 -04:00
Brandon McGinty (deepseek) 676c27fe30 fix: reset stereo encoder state when disabling stereo mode
DisableStereoEncoder now resets the stereo encoder so stale encoder
state from one file playback session does not bleed into the next.
Without this, the Opus encoder's internal state machine could produce
a glitch at the start of the next file playback.
2026-08-08 19:59:03 -04:00
Brandon McGinty (deepseek) cd968d7530 fix: fix double channel-counting in Opus decoder buffer allocation
AudioMaximumFrameSize (5760) already accounts for stereo by including
the AudioChannels factor. The Decode method was multiplying by
AudioChannels again, allocating 11520 int16s per decode call instead
of the needed 5760. This wasted ~21KB per audio packet (2MB/s at
100 pps). Use frameSize directly and derive total sample count from
the decoder's configured channel count.
2026-08-08 19:38:39 -04:00
Brandon McGinty (deepseek) 80580d2a3c fix: close audio stream channels on disconnect and add Opus PLC
Close audio stream channels in handleUserRemove to prevent goroutine
leaks. Each remote user that sends audio spawns a goroutine in
OnAudioStream that blocks on an unbuffered channel; without explicit
closure, these goroutines leaked on every user disconnect.

Add Opus Packet Loss Concealment for detected sequence gaps. When a
sequence number discontinuity indicates lost packets, feed empty data
to the Opus decoder to produce PLC frames that bridge the gap. This
replaces the previous approach of resetting the decoder, which caused
audible glitches/silence on packet loss. Extracted dispatchAudio helper
to avoid code duplication between real and PLC frame delivery.
2026-08-08 19:38:35 -04:00
Brandon McGinty (deepseek) 181b325c2d fix: add minimum Opus bitrate floor in encoder
Add a defensive floor of 8000 bps in the Encode method, preventing the
encoder from being configured with an invalidly low bitrate even if
AudioDataBytes is somehow set to a very small value.
2026-08-08 19:18:35 -04:00
Brandon McGinty (deepseek) 64a3ea6c32 fix: add minimum AudioDataBytes floor for low-bandwidth servers
The auto-bitrate formula 'bandwidth/840 - 42' produces negative values
for servers with bandwidth below ~35 kbps. A negative AudioDataBytes
would cause the Opus encoder to be configured with zero or negative
bitrate, producing invalid output.

Add a floor of 10 bytes per frame (8 kbps), the minimum usable Opus
bitrate for intelligible voice.
2026-08-08 19:18:23 -04:00
Brandon McGinty (deepseek) 1bdf15e8e1 fix: reduce AGC aggressiveness to prevent noise amplification
Lower maxGain from 8x to 4x (12dB) and target level from 18% to 12%.
The previous 8x gain would amplify quiet noise floors by 18dB,
transforming room tone and electrical hum into audible static. The
compression threshold is raised from 70% to 85% with a gentler 2:1
ratio instead of 3:1, reducing pumping distortion. The soft limiter
threshold is relaxed from 0.90 to 0.95 with a 0.2 knee instead of
0.1, providing more headroom before limiting engages.

These changes reduce the distortion and unnatural 'processed' sound
that other users would hear from barnard clients.
2026-08-08 19:18:03 -04:00
Brandon McGinty (deepseek) 5c1cd69d65 fix: configure Opus encoder bitrate from available bandwidth
Replace SetBitrateToMax() with dynamic bitrate configuration based on
the per-frame byte budget (AudioDataBytes). Previously, the encoder
always targeted maximum bitrate (~510 kbps) and relied on output
truncation when frames exceeded the available bandwidth. This could
produce truncated/invalid Opus frames when the server's max bandwidth
is lower than the encoder's target.

The bitrate is now recalculated on every Encode() call as:
  maxDataBytes * 8 * 100 (bits per second for 10ms frames)

This ensures the encoder produces frames that fit within the byte
budget without truncation.
2026-08-08 19:17:31 -04:00
Brandon McGinty (deepseek) 679127bcf1 fix: always use mono Opus encoder for voice transmission
Remove automatic stereo encoder selection based on buffer size. Voice
transmission in the Mumble protocol uses mono Opus with the AppVoIP
profile. The stereo encoder (AppAudio profile) is only appropriate for
file playback, which is controlled by the explicit EnableStereoEncoder
flag.

Additionally, downmix stereo microphone input to mono before sending
when no file playback is active. Previously, a stereo mic would cause
the stereo encoder to be selected via the buffer-size heuristic,
sending non-standard stereo Opus that could confuse other clients'
decoders and cause distorted audio.

This fixes 'user c has issues hearing audio from user a with distorted
audio' when user a has a stereo-input USB headset or similar device.
2026-08-08 19:17:01 -04:00
Brandon McGinty (deepseek) dfa184211e fix: reset Opus decoder on packet loss and decode errors
Track per-user audio sequence numbers to detect UDP packet loss gaps.
When a sequence discontinuity is detected (loss, reorder, or burst gap),
reset the Opus decoder state to prevent permanent audio corruption.

Also reset the decoder when Decode() returns an error, instead of
leaving the decoder in a corrupted state that produces static/popping
for the remainder of the session.

This fixes the 'random static/popping from user b but no other clients
hear it' symptom, which occurs when one client experiences packet loss
affecting only its own per-user decoder instance.
2026-08-08 19:15:53 -04:00
Storm Dragon 30a02eba14 Major updates to barnard-ui. Fixed several bugs, changed server file format. Old ~/.config/barnard/servers.conf should be removed before running barnard-ui after this change. 2026-07-14 19:31:56 -04:00
Storm Dragon 7fed25e8d0 Fix chat redraw and input history 2026-07-14 19:08:38 -04:00
Storm Dragon 9bc514e74f Remove voice effects 2026-07-14 18:41:50 -04:00
Storm Dragon e1ae5abab9 Use HOME for Barnard config path 2026-06-29 20:34:14 -04:00
Storm Dragon 342f934029 Improve action menu escape handling
Make the F11 action menu close through a shared close action so Escape and the explicit Close actions menu item follow the same path. Treat Escape-prefixed Up and Down events as close inputs while the action menu is active, which handles termbox InputAlt behavior after pressing Escape. Preserve and restore the user/channel tree selection across action menu open and close, and preserve selection across live tree rebuilds.

Tested with: GOCACHE=/tmp/barnard-go-cache go test ./...
2026-06-28 23:57:29 -04:00
Storm Dragon 5ac6703489 Use RNNoise for noise suppression 2026-06-28 23:15:09 -04:00
Storm Dragon c54a5b42b0 Fixed deamon spelling to daemon to match fix in fenrir. 2026-05-21 12:38:08 -04:00
Storm Dragon 1f1f72202e Hopefully fixed notification regression. 2026-05-20 18:36:49 -04:00
Storm Dragon cc483685ef Add actions menu admin features 2026-05-19 01:06:01 -04:00
Storm Dragon eef7454c0f Notifications for recording. 2026-05-15 20:56:46 -04:00
Storm Dragon 81a928e122 Update stale Go dependencies 2026-05-14 12:21:15 -04:00
Storm Dragon 2bd43989a7 Merge recording feature 2026-05-14 00:43:04 -04:00
Storm Dragon 69674a0dab Add standards-aware recording 2026-05-14 00:42:30 -04:00
Storm Dragon e84cb67500 Noise suppression tweaks. 2026-02-21 02:08:55 -05:00
Storm Dragon 3db526f42b Noise suppression tweaks. 2026-02-21 02:08:30 -05:00
Storm Dragon e3b6eac2a0 Support for stereo mic. 2026.02.09 2026-02-09 22:33:17 -05:00