Handle legacy UDPVoiceOpus format (type 0x80) inside 1.5 crypto

The server sends audio with type byte 0x80 (legacy UDPVoiceOpus) inside
the Mumble 1.5 native UDP crypto envelope. This is the legacy format:
[session varint][seq varint][length varint][opus data].

Added handleLegacyUDPVoice to parse this format. Refactored dispatch
into dispatchOpus15 + decodeAndDispatch for reuse by both 0x00 protobuf
and 0x80 legacy paths.
This commit is contained in:
Brandon McGinty (deepseek)
2026-08-09 02:40:48 -04:00
committed by Brandon McGinty
parent e6806a7521
commit e93437a212
+70 -13
View File
@@ -559,27 +559,37 @@ func (c *Client) HandleUDPPacket15(packet []byte, pktNum uint64) {
log.Info("UDP15 #%d: ping response, ignoring", pktNum)
return
}
if msgType != 0x00 {
log.Warn("UDP15 #%d: unknown message type 0x%02x", pktNum, msgType)
// MumbleUDP.Audio protobuf format (1.5 native).
if msgType == 0x00 {
session, frameNum, opusData, terminator := decodeUDPAudio(plaintext)
c.dispatchOpus15(pktNum, session, int64(frameNum), opusData, terminator)
return
}
// Parse MumbleUDP.Audio protobuf.
session, frameNum, opusData, terminator := decodeUDPAudio(plaintext)
// Legacy UDPVoiceOpus format: type byte has bits 5-7 = 4.
if (msgType >> 5) == 4 {
c.handleLegacyUDPVoice(pktNum, plaintext)
return
}
log.Warn("UDP15 #%d: unknown message type 0x%02x", pktNum, msgType)
}
// dispatchOpus15 processes a decoded MumbleUDP.Audio frame and dispatches
// the decoded PCM to audio listeners.
func (c *Client) dispatchOpus15(pktNum uint64, session uint32, frameNum int64, opusData []byte, terminator bool) {
if len(opusData) == 0 && !terminator {
log.Info("UDP15 #%d: no opus data (session=%d frame=%d), skipping", pktNum, session, frameNum)
return
}
// Find the user.
user := c.Users[session]
if user == nil {
log.Warn("UDP15 #%d: unknown session %d", pktNum, session)
return
}
// Get or create decoder.
decoder := user.decoder
if decoder == nil {
codec := c.audioCodec
@@ -593,7 +603,6 @@ func (c *Client) HandleUDPPacket15(packet []byte, pktNum uint64) {
}
if terminator && len(opusData) == 0 {
// Decoder reset on terminator.
decoder.Reset()
log.Info("UDP15 #%d: terminator for %s, decoder reset", pktNum, user.Name)
return
@@ -603,9 +612,59 @@ func (c *Client) HandleUDPPacket15(packet []byte, pktNum uint64) {
return
}
// Detect sequence gaps via frame_number.
c.decodeAndDispatch(pktNum, user, decoder, frameNum, opusData)
}
// handleLegacyUDPVoice parses the legacy UDPVoice format (type byte 0x80)
// inside a 1.5-decrypted payload.
func (c *Client) handleLegacyUDPVoice(pktNum uint64, data []byte) {
pos := 0
// Session varint.
session, n := varint.Decode(data[pos:])
if n <= 0 {
log.Warn("UDP15 #%d: legacy session varint decode failed", pktNum)
return
}
pos += n
// Sequence varint.
seq, n := varint.Decode(data[pos:])
if n <= 0 {
log.Warn("UDP15 #%d: legacy seq varint decode failed", pktNum)
return
}
pos += n
// Length varint (bit 13 = terminator).
length, n := varint.Decode(data[pos:])
if n <= 0 {
log.Warn("UDP15 #%d: legacy length varint decode failed", pktNum)
return
}
pos += n
terminator := (length & 0x2000) != 0
audioLen := int(length &^ 0x2000)
if audioLen > len(data)-pos {
log.Warn("UDP15 #%d: legacy audio length %d > remaining %d",
pktNum, audioLen, len(data)-pos)
return
}
opusData := data[pos : pos+audioLen]
log.Info("UDP15 #%d: legacy voice session=%d seq=%d opus_len=%d term=%v",
pktNum, session, seq, len(opusData), terminator)
c.dispatchOpus15(pktNum, uint32(session), seq, opusData, terminator)
}
// decodeAndDispatch decodes an Opus frame and dispatches PCM to audio listeners.
func (c *Client) decodeAndDispatch(pktNum uint64, user *User, decoder AudioDecoder, frameNum int64, opusData []byte) {
// Detect sequence gaps.
if user.audioSequenceValid {
gap := int64(frameNum) - user.audioSequence
gap := frameNum - user.audioSequence
if gap > 1 && gap < 100 {
log.Info("UDP15 #%d: seq gap for %s: %d -> %d (loss=%d), generating PLC",
pktNum, user.Name, user.audioSequence, frameNum, gap-1)
@@ -621,10 +680,9 @@ func (c *Client) HandleUDPPacket15(packet []byte, pktNum uint64) {
return
}
}
user.audioSequence = int64(frameNum)
user.audioSequence = frameNum
user.audioSequenceValid = true
// Decode Opus.
pcm, err := decoder.Decode(opusData, AudioMaximumFrameSize)
if err != nil {
log.Warn("UDP15 #%d: Opus decode failed for %s: %v", pktNum, user.Name, err)
@@ -634,12 +692,11 @@ func (c *Client) HandleUDPPacket15(packet []byte, pktNum uint64) {
log.Info("UDP15 #%d: Opus OK for %s, pcm_samples=%d", pktNum, user.Name, len(pcm))
// Dispatch.
event := AudioPacket{
Client: c,
Sender: user,
Target: &VoiceTarget{ID: 0},
Sequence: int64(frameNum),
Sequence: frameNum,
AudioBuffer: AudioBuffer(pcm),
}
c.dispatchAudio(user, &event)