diff --git a/gumble/gumble/udp.go b/gumble/gumble/udp.go index c10ff6b..22fb675 100644 --- a/gumble/gumble/udp.go +++ b/gumble/gumble/udp.go @@ -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()) diff --git a/gumble/gumble/udp15.go b/gumble/gumble/udp15.go index 95c7859..e4e8f98 100644 --- a/gumble/gumble/udp15.go +++ b/gumble/gumble/udp15.go @@ -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.