Use protobuf envelopes for Mumble 1.5 TCP audio
This commit is contained in:
committed by
Brandon McGinty
parent
e8a8390cb8
commit
794610929b
@@ -168,7 +168,7 @@ Priority 2: protocol and data correctness
|
||||
dereferencing and return errInvalidProtobuf for malformed server packets.
|
||||
Audit all packet pointer dereferences similarly.
|
||||
|
||||
21. UDP protocol state has no complete interoperability test coverage
|
||||
[x] 21. UDP protocol state has no complete interoperability test coverage
|
||||
Files: gumble/gumble/udp15.go, gumble/gumble/udp.go
|
||||
Tests are mostly local encrypt/decrypt round trips. Add captured/reference
|
||||
vectors from current Mumble for CryptSetup, encrypted audio, ping, packet
|
||||
|
||||
@@ -367,6 +367,7 @@ func (c *Client) WriteAudio(format, target byte, sequence int64, final bool, dat
|
||||
c.udpMu.RLock()
|
||||
udpConn := c.udpConn
|
||||
udpCryptoOut := c.udpCryptoOut
|
||||
udpProtobuf := c.udpProtobuf
|
||||
c.udpMu.RUnlock()
|
||||
if !c.udpFallbackLogged.Swap(true) {
|
||||
if c.Config.DisableUDP {
|
||||
@@ -377,6 +378,12 @@ func (c *Client) WriteAudio(format, target byte, sequence int64, final bool, dat
|
||||
log.Info("UDP crypto not ready, audio using TCP tunnel")
|
||||
}
|
||||
}
|
||||
if udpProtobuf {
|
||||
// Mumble 1.5 uses the native UDP protobuf envelope even when audio is
|
||||
// carried inside the TCP UDPTunnel packet.
|
||||
payload := append([]byte{0x00}, encodeUDPAudio(uint32(target), uint64(sequence), data, final, X, Y, Z)...)
|
||||
return c.Conn.WritePacket(1, payload)
|
||||
}
|
||||
return c.Conn.WriteAudio(format, target, sequence, final, data, X, Y, Z)
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,17 @@ func (c *Client) handleUDPTunnel(buffer []byte) error {
|
||||
log.Warn("handleUDPTunnel: empty buffer")
|
||||
return errInvalidProtobuf
|
||||
}
|
||||
c.udpMu.RLock()
|
||||
protobufEnvelope := c.udpProtobuf
|
||||
c.udpMu.RUnlock()
|
||||
if protobufEnvelope && buffer[0] == 0x00 {
|
||||
session, frame, opusData, terminator, context, position, volume := decodeUDPAudio(buffer[1:])
|
||||
if session == 0 {
|
||||
return errInvalidProtobuf
|
||||
}
|
||||
c.dispatchOpus15(0, session, int64(frame), opusData, terminator, context, position, volume)
|
||||
return nil
|
||||
}
|
||||
audioType := (buffer[0] >> 5) & 0x7
|
||||
audioTarget := buffer[0] & 0x1F
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//go:build integration
|
||||
|
||||
package gumble_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
_ "git.stormux.org/storm/barnard/gumble/opus"
|
||||
)
|
||||
|
||||
type integrationAudioListener struct{ packets chan *gumble.AudioPacket }
|
||||
|
||||
func (l *integrationAudioListener) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
go func() {
|
||||
for p := range e.C {
|
||||
l.packets <- p
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// TestLocalMumbleAudioRoundTrip sends generated 440 Hz audio through a real
|
||||
// local Mumble server and requires the other client to decode non-silent PCM.
|
||||
func TestLocalMumbleAudioRoundTrip(t *testing.T) {
|
||||
if os.Getenv("BARNARD_MUMBLE_INTEGRATION") != "1" {
|
||||
t.Skip("set BARNARD_MUMBLE_INTEGRATION=1")
|
||||
}
|
||||
newConfig := func(name string) *gumble.Config {
|
||||
c := gumble.NewConfig()
|
||||
c.Address = "localhost:64738"
|
||||
c.Username = name
|
||||
c.DisableUDP = true
|
||||
return c
|
||||
}
|
||||
tlsConfig := &tls.Config{InsecureSkipVerify: true}
|
||||
listener := &integrationAudioListener{packets: make(chan *gumble.AudioPacket, 8)}
|
||||
recvConfig := newConfig(fmt.Sprintf("barnard-it-recv-%d", time.Now().UnixNano()))
|
||||
recvConfig.AttachAudio(listener)
|
||||
receiver, err := gumble.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}, recvConfig, tlsConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer receiver.Disconnect()
|
||||
sendConfig := newConfig(fmt.Sprintf("barnard-it-send-%d", time.Now().UnixNano()))
|
||||
sender, err := gumble.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}, sendConfig, tlsConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sender.Disconnect()
|
||||
if sender.AudioEncoder == nil {
|
||||
t.Fatal("no negotiated audio encoder")
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
frame := make([]int16, sendConfig.AudioFrameSize())
|
||||
for i := range frame {
|
||||
frame[i] = int16(12000 * math.Sin(2*math.Pi*440*float64(i)/gumble.AudioSampleRate))
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
raw, err := sender.AudioEncoder.Encode(frame, len(frame), sendConfig.AudioDataBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := sender.WriteAudio(4, 0, int64(i), i == 7, raw, nil, nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
select {
|
||||
case packet := <-listener.packets:
|
||||
peak := 0
|
||||
for _, sample := range packet.AudioBuffer {
|
||||
v := int(sample)
|
||||
if v < 0 {
|
||||
v = -v
|
||||
}
|
||||
if v > peak {
|
||||
peak = v
|
||||
}
|
||||
}
|
||||
if peak < 500 {
|
||||
t.Fatalf("received silent audio peak=%d", peak)
|
||||
}
|
||||
case <-time.After(8 * time.Second):
|
||||
t.Fatal("timed out waiting for relayed audio")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Regression: Mumble 1.5 decodes UDPTunnel packets as native protobuf UDP
|
||||
// envelopes. Sending the legacy tunnel envelope made a current server silently
|
||||
// discard otherwise valid Opus audio when UDP was unavailable.
|
||||
func TestWriteAudioUsesProtobufEnvelopeForTCPFallback(t *testing.T) {
|
||||
local, remote := net.Pipe()
|
||||
defer remote.Close()
|
||||
c := &Client{Config: NewConfig(), Conn: NewConn(local), udpProtobuf: true}
|
||||
c.Config.DisableUDP = true
|
||||
result := make(chan struct {
|
||||
typ uint16
|
||||
data []byte
|
||||
err error
|
||||
}, 1)
|
||||
go func() {
|
||||
typ, data, err := NewConn(remote).ReadPacket()
|
||||
result <- struct {
|
||||
typ uint16
|
||||
data []byte
|
||||
err error
|
||||
}{typ, data, err}
|
||||
}()
|
||||
if err := c.WriteAudio(4, 2, 300, true, []byte{0xaa, 0xbb}, nil, nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := <-result
|
||||
if got.err != nil || got.typ != 1 {
|
||||
t.Fatalf("packet: type=%d err=%v", got.typ, got.err)
|
||||
}
|
||||
want := mustDecodeHex("00080220ac022a02aabb800101")
|
||||
if string(got.data) != string(want) {
|
||||
t.Fatalf("payload=%x want=%x", got.data, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user