Fix protobuf varint encoding in 1.5 UDP protocol
The 1.5 UDP protocol uses standard Google protobuf varint encoding, but encodeUDPAudio, decodeUDPAudio, and sendUDPPing were using Mumble's custom varint (from the legacy UDP format). These are different encodings for values >= 128, which includes: - The field-16 (is_terminator) tag: (16<<3)|0 = 128 - Opus data lengths > 127 - Ping timestamps The server's protobuf parser (ParseFromArray) would see garbage lengths (e.g. 622 bytes of opus data would be decoded as a 14082-byte field, causing parse failure). Packets were silently dropped. Also add the target field (field 1, value 0 = normal speech) to outbound audio, matching the reference client's encodeAudioPacket_protobuf. Also bump ClientVersion from 1.3.0 to 1.5.0 so the server uses the protobuf decoder for our audio packets instead of the legacy decoder.
This commit is contained in:
committed by
Brandon McGinty
parent
e61ebdae38
commit
0f2eb4408f
@@ -6,7 +6,6 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/varint"
|
||||
"git.stormux.org/storm/barnard/log"
|
||||
)
|
||||
|
||||
@@ -78,20 +77,21 @@ func (c *Client) udpPingRoutine() {
|
||||
}
|
||||
|
||||
// sendUDPPing sends a Mumble 1.5 native UDP ping.
|
||||
// Uses standard protobuf varint encoding (not Mumble's custom varint).
|
||||
func (c *Client) sendUDPPing() {
|
||||
cs := udp15Client
|
||||
if cs == nil || !cs.initialized || c.udpConn == nil {
|
||||
return
|
||||
}
|
||||
// Type byte 0x01 = UDPPing, field 1 = timestamp (varint, milliseconds).
|
||||
var tmp [varint.MaxVarintLen]byte
|
||||
// Type byte 0x01 = UDPPing, field 1 = timestamp (protobuf varint, milliseconds).
|
||||
var tmp [10]byte // max protobuf varint size
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte(0x01) // type = UDPPing
|
||||
|
||||
n := varint.Encode(tmp[:], int64((1<<3)|0))
|
||||
n := pbEncodeVarint(tmp[:], uint64((1<<3)|0))
|
||||
buf.Write(tmp[:n]) // field 1 tag
|
||||
|
||||
n = varint.Encode(tmp[:], int64(time.Now().UnixMilli()))
|
||||
n = pbEncodeVarint(tmp[:], uint64(time.Now().UnixMilli()))
|
||||
buf.Write(tmp[:n]) // timestamp value
|
||||
|
||||
encrypted, err := cs.encrypt15(buf.Bytes())
|
||||
|
||||
+58
-13
@@ -10,6 +10,42 @@ import (
|
||||
"git.stormux.org/storm/barnard/log"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Protobuf varint helpers.
|
||||
// The 1.5 UDP protocol uses standard Google protobuf varint encoding,
|
||||
// NOT Mumble's custom varint (which is used in the legacy UDP format).
|
||||
// The Mumble custom varint is in ../varint/; protobuf varint is below.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// pbEncodeVarint writes v as a protobuf varint into buf and returns the
|
||||
// number of bytes written. buf must have sufficient space (10 bytes for
|
||||
// a full uint64).
|
||||
func pbEncodeVarint(buf []byte, v uint64) int {
|
||||
i := 0
|
||||
for v >= 0x80 {
|
||||
buf[i] = byte(v) | 0x80
|
||||
v >>= 7
|
||||
i++
|
||||
}
|
||||
buf[i] = byte(v)
|
||||
return i + 1
|
||||
}
|
||||
|
||||
// pbDecodeVarint reads a protobuf varint from buf and returns the value
|
||||
// and the number of bytes consumed (0 on error).
|
||||
func pbDecodeVarint(buf []byte) (uint64, int) {
|
||||
var v uint64
|
||||
var s uint
|
||||
for i, b := range buf {
|
||||
v |= uint64(b&0x7F) << s
|
||||
if b < 0x80 {
|
||||
return v, i + 1
|
||||
}
|
||||
s += 7
|
||||
}
|
||||
return 0, 0 // truncated
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mumble 1.5 native UDP — MumbleUDP.Audio protobuf helpers.
|
||||
//
|
||||
@@ -22,31 +58,39 @@ import (
|
||||
|
||||
// encodeUDPAudio builds a MumbleUDP.Audio protobuf message.
|
||||
// If session == 0, sender_session is omitted (used for outbound).
|
||||
// Uses standard protobuf varint encoding, not Mumble's custom varint.
|
||||
func encodeUDPAudio(session uint32, frameNumber uint32, opusData []byte, terminator bool) []byte {
|
||||
var buf bytes.Buffer
|
||||
var tmp [varint.MaxVarintLen]byte
|
||||
var tmp [10]byte // max protobuf varint size
|
||||
|
||||
// Field 1: target = 0 (normal speech). Always encoded so the server
|
||||
// sees the oneof Header choice, matching the reference client.
|
||||
n := pbEncodeVarint(tmp[:], uint64((1<<3)|0))
|
||||
buf.Write(tmp[:n])
|
||||
n = pbEncodeVarint(tmp[:], 0)
|
||||
buf.Write(tmp[:n])
|
||||
|
||||
if session != 0 {
|
||||
n := varint.Encode(tmp[:], int64((3<<3)|0))
|
||||
n := pbEncodeVarint(tmp[:], uint64((3<<3)|0))
|
||||
buf.Write(tmp[:n])
|
||||
n = varint.Encode(tmp[:], int64(session))
|
||||
n = pbEncodeVarint(tmp[:], uint64(session))
|
||||
buf.Write(tmp[:n])
|
||||
}
|
||||
n := varint.Encode(tmp[:], int64((4<<3)|0))
|
||||
n = pbEncodeVarint(tmp[:], uint64((4<<3)|0))
|
||||
buf.Write(tmp[:n])
|
||||
n = varint.Encode(tmp[:], int64(frameNumber))
|
||||
n = pbEncodeVarint(tmp[:], uint64(frameNumber))
|
||||
buf.Write(tmp[:n])
|
||||
if len(opusData) > 0 {
|
||||
n := varint.Encode(tmp[:], int64((5<<3)|2))
|
||||
n := pbEncodeVarint(tmp[:], uint64((5<<3)|2))
|
||||
buf.Write(tmp[:n])
|
||||
n = varint.Encode(tmp[:], int64(len(opusData)))
|
||||
n = pbEncodeVarint(tmp[:], uint64(len(opusData)))
|
||||
buf.Write(tmp[:n])
|
||||
buf.Write(opusData)
|
||||
}
|
||||
if terminator {
|
||||
n := varint.Encode(tmp[:], int64((16<<3)|0))
|
||||
n := pbEncodeVarint(tmp[:], uint64((16<<3)|0))
|
||||
buf.Write(tmp[:n])
|
||||
n = varint.Encode(tmp[:], 1)
|
||||
n = pbEncodeVarint(tmp[:], 1)
|
||||
buf.Write(tmp[:n])
|
||||
}
|
||||
|
||||
@@ -54,10 +98,11 @@ func encodeUDPAudio(session uint32, frameNumber uint32, opusData []byte, termina
|
||||
}
|
||||
|
||||
// decodeUDPAudio parses a MumbleUDP.Audio protobuf message.
|
||||
// Uses standard protobuf varint decoding, not Mumble's custom varint.
|
||||
func decodeUDPAudio(data []byte) (session uint32, frameNumber uint32, opusData []byte, terminator bool) {
|
||||
pos := 0
|
||||
for pos < len(data) {
|
||||
key, n := varint.Decode(data[pos:])
|
||||
key, n := pbDecodeVarint(data[pos:])
|
||||
if n <= 0 {
|
||||
break
|
||||
}
|
||||
@@ -67,7 +112,7 @@ func decodeUDPAudio(data []byte) (session uint32, frameNumber uint32, opusData [
|
||||
|
||||
switch wireType {
|
||||
case 0: // varint
|
||||
val, n := varint.Decode(data[pos:])
|
||||
val, n := pbDecodeVarint(data[pos:])
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
@@ -81,7 +126,7 @@ func decodeUDPAudio(data []byte) (session uint32, frameNumber uint32, opusData [
|
||||
terminator = val != 0
|
||||
}
|
||||
case 2: // length-delimited
|
||||
length, n := varint.Decode(data[pos:])
|
||||
length, n := pbDecodeVarint(data[pos:])
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
@@ -495,7 +540,7 @@ func (c *Client) WriteAudioUDP15(data []byte, final bool) (bool, error) {
|
||||
frameNum := nextFrameNumber()
|
||||
|
||||
// Build MumbleUDP.Audio: type byte 0x00 + protobuf.
|
||||
// Matches wumble: Bytes[0_u8] + Protobuf.field(4, ...) + Protobuf.bytes(5, ...)
|
||||
// Matches reference client: 0x00 + target=0 + frame_number + opus_data [+ is_terminator]
|
||||
payload := append([]byte{0x00}, encodeUDPAudio(0, frameNum, data, final)...)
|
||||
|
||||
// Encrypt with 1.5 format.
|
||||
|
||||
Reference in New Issue
Block a user