├── .gitignore
├── misc
├── gumble.dia
└── gumble.svg
├── gumble
├── MumbleProto
│ ├── generate.go
│ ├── generate_main.go
│ └── LICENSE
├── detacher.go
├── contextactions.go
├── message.go
├── accesstokens.go
├── rpwmutex.go
├── varint
│ ├── varint_test.go
│ ├── read.go
│ └── write.go
├── version.go
├── channels.go
├── permission.go
├── users.go
├── audiolisteners.go
├── textmessage.go
├── audiocodec.go
├── doc.go
├── userstats.go
├── config.go
├── contextaction.go
├── userlist.go
├── reject.go
├── voicetarget.go
├── audio.go
├── bans.go
├── ping.go
├── acl.go
├── listeners.go
├── conn.go
├── channel.go
├── user.go
├── event.go
├── client.go
└── handlers.go
├── _examples
└── mumble-audio-player
│ ├── chimes.ogg
│ └── main.go
├── gumbleutil
├── doc.go
├── channel.go
├── bitrate.go
├── textmessage.go
├── acl.go
├── main.go
├── listenerfunc.go
└── listener.go
├── go.mod
├── go.sum
├── README.md
├── opus
└── opus.go
├── cmd
└── mumble-ping
│ └── main.go
├── gumbleffmpeg
├── source.go
└── stream.go
├── gumbleopenal
└── stream.go
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE
2 | /.idea/
3 |
--------------------------------------------------------------------------------
/misc/gumble.dia:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/layeh/gumble/HEAD/misc/gumble.dia
--------------------------------------------------------------------------------
/gumble/MumbleProto/generate.go:
--------------------------------------------------------------------------------
1 | //go:generate go run generate_main.go
2 | package MumbleProto
3 |
--------------------------------------------------------------------------------
/_examples/mumble-audio-player/chimes.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/layeh/gumble/HEAD/_examples/mumble-audio-player/chimes.ogg
--------------------------------------------------------------------------------
/gumbleutil/doc.go:
--------------------------------------------------------------------------------
1 | // Package gumbleutil provides extras that can make working with gumble easier.
2 | package gumbleutil
3 |
--------------------------------------------------------------------------------
/gumble/detacher.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | // Detacher is an interface that event listeners implement. After the Detach
4 | // method is called, the listener will no longer receive events.
5 | type Detacher interface {
6 | Detach()
7 | }
8 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module layeh.com/gumble
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372
7 | github.com/golang/protobuf v1.3.1
8 | layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa
9 | )
10 |
--------------------------------------------------------------------------------
/gumble/contextactions.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | // ContextActions is a map of ContextActions.
4 | type ContextActions map[string]*ContextAction
5 |
6 | func (c ContextActions) create(action string) *ContextAction {
7 | contextAction := &ContextAction{
8 | Name: action,
9 | }
10 | c[action] = contextAction
11 | return contextAction
12 | }
13 |
--------------------------------------------------------------------------------
/gumble/message.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | // Message is data that be encoded and sent to the server. The following
4 | // types implement this interface:
5 | // AccessTokens
6 | // ACL
7 | // BanList
8 | // RegisteredUsers
9 | // TextMessage
10 | // VoiceTarget
11 | type Message interface {
12 | writeMessage(client *Client) error
13 | }
14 |
--------------------------------------------------------------------------------
/gumble/accesstokens.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "layeh.com/gumble/gumble/MumbleProto"
5 | )
6 |
7 | // AccessTokens are additional passwords that can be provided to the server to
8 | // gain access to restricted channels.
9 | type AccessTokens []string
10 |
11 | func (a AccessTokens) writeMessage(client *Client) error {
12 | packet := MumbleProto.Authenticate{
13 | Tokens: a,
14 | }
15 | return client.Conn.WriteProto(&packet)
16 | }
17 |
--------------------------------------------------------------------------------
/gumbleutil/channel.go:
--------------------------------------------------------------------------------
1 | package gumbleutil
2 |
3 | import (
4 | "layeh.com/gumble/gumble"
5 | )
6 |
7 | // ChannelPath returns a slice of channel names, starting from the root channel
8 | // to the given channel.
9 | func ChannelPath(channel *gumble.Channel) []string {
10 | var pieces []string
11 | for ; channel != nil; channel = channel.Parent {
12 | pieces = append(pieces, channel.Name)
13 | }
14 | for i := 0; i < (len(pieces) / 2); i++ {
15 | pieces[len(pieces)-1-i], pieces[i] = pieces[i], pieces[len(pieces)-1-i]
16 | }
17 | return pieces
18 | }
19 |
--------------------------------------------------------------------------------
/gumble/rpwmutex.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import "sync"
4 |
5 | // rpwMutex is a reader-preferred RWMutex.
6 | type rpwMutex struct {
7 | w sync.Mutex
8 | r sync.Mutex
9 | n int
10 | }
11 |
12 | func (m *rpwMutex) Lock() {
13 | m.w.Lock()
14 | }
15 |
16 | func (m *rpwMutex) Unlock() {
17 | m.w.Unlock()
18 | }
19 |
20 | func (m *rpwMutex) RLock() {
21 | m.r.Lock()
22 | m.n++
23 | if m.n == 1 {
24 | m.w.Lock()
25 | }
26 | m.r.Unlock()
27 | }
28 |
29 | func (m *rpwMutex) RUnlock() {
30 | m.r.Lock()
31 | m.n--
32 | if m.n == 0 {
33 | m.w.Unlock()
34 | }
35 | m.r.Unlock()
36 | }
37 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372 h1:tz3KnXWtRZR0RWOfcMNOw+HHezWLQa7vfSOWTtKjchI=
2 | github.com/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372/go.mod h1:74z+CYu2/mx4N+mcIS/rsvfAxBPBV9uv8zRAnwyFkdI=
3 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
4 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
5 | layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa h1:WNU4LYsgD2UHxgKgB36mL6iMAMOvr127alafSlgBbiA=
6 | layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa/go.mod h1:AOef7vHz0+v4sWwJnr0jSyHiX/1NgsMoaxl+rEPz/I0=
7 |
--------------------------------------------------------------------------------
/gumble/varint/varint_test.go:
--------------------------------------------------------------------------------
1 | package varint
2 |
3 | import "testing"
4 |
5 | func TestRange(t *testing.T) {
6 |
7 | fn := func(i int64) {
8 | var b [MaxVarintLen]byte
9 | size := Encode(b[:], i)
10 | if size == 0 {
11 | t.Error("Encode returned size 0\n")
12 | }
13 | s := b[:size]
14 |
15 | val, size := Decode(s)
16 | if size == 0 {
17 | t.Error("Decode return size 0\n")
18 | }
19 |
20 | if i != val {
21 | t.Errorf("Encoded %d (%v) equals decoded %d\n", i, s, val)
22 | }
23 | }
24 |
25 | for i := int64(-10000); i <= 10000; i++ {
26 | fn(i)
27 | }
28 |
29 | fn(134342525)
30 | fn(10282934828342)
31 | fn(1028293482834200000)
32 | }
33 |
--------------------------------------------------------------------------------
/gumbleutil/bitrate.go:
--------------------------------------------------------------------------------
1 | package gumbleutil
2 |
3 | import (
4 | "time"
5 |
6 | "layeh.com/gumble/gumble"
7 | )
8 |
9 | var autoBitrate = &Listener{
10 | Connect: func(e *gumble.ConnectEvent) {
11 | if e.MaximumBitrate != nil {
12 | const safety = 5
13 | interval := e.Client.Config.AudioInterval
14 | dataBytes := (*e.MaximumBitrate / (8 * (int(time.Second/interval) + safety))) - 32 - 10
15 |
16 | e.Client.Config.AudioDataBytes = dataBytes
17 | }
18 | },
19 | }
20 |
21 | // AutoBitrate is a gumble.EventListener that automatically sets the client's
22 | // AudioDataBytes to suitable value, based on the server's bitrate.
23 | var AutoBitrate gumble.EventListener
24 |
25 | func init() {
26 | AutoBitrate = autoBitrate
27 | }
28 |
--------------------------------------------------------------------------------
/gumble/version.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | // Version represents a Mumble client or server version.
4 | type Version struct {
5 | // The semantic version information as a single unsigned integer.
6 | //
7 | // Bits 0-15 are the major version, bits 16-23 are the minor version, and
8 | // bits 24-31 are the patch version.
9 | Version uint32
10 | // The name of the client.
11 | Release string
12 | // The operating system name.
13 | OS string
14 | // The operating system version.
15 | OSVersion string
16 | }
17 |
18 | // SemanticVersion returns the version's semantic version components.
19 | func (v *Version) SemanticVersion() (major uint16, minor, patch uint8) {
20 | major = uint16(v.Version>>16) & 0xFFFF
21 | minor = uint8(v.Version>>8) & 0xFF
22 | patch = uint8(v.Version) & 0xFF
23 | return
24 | }
25 |
--------------------------------------------------------------------------------
/gumble/channels.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | // Channels is a map of server channels.
4 | type Channels map[uint32]*Channel
5 |
6 | // create adds a new channel with the given id to the collection. If a channel
7 | // with the given id already exists, it is overwritten.
8 | func (c Channels) create(id uint32) *Channel {
9 | channel := &Channel{
10 | ID: id,
11 | Links: Channels{},
12 | Children: Channels{},
13 | Users: Users{},
14 | }
15 | c[id] = channel
16 | return channel
17 | }
18 |
19 | // Find returns a channel whose path (by channel name) from the server root
20 | // channel is equal to the arguments passed. nil is returned if c does not
21 | // containt the root channel.
22 | func (c Channels) Find(names ...string) *Channel {
23 | root := c[0]
24 | if names == nil || root == nil {
25 | return root
26 | }
27 | return root.Find(names...)
28 | }
29 |
--------------------------------------------------------------------------------
/gumble/permission.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | // Permission is a bitmask of permissions given to a certain user.
4 | type Permission int
5 |
6 | // Permissions that can be applied in any channel.
7 | const (
8 | PermissionWrite Permission = 1 << iota
9 | PermissionTraverse
10 | PermissionEnter
11 | PermissionSpeak
12 | PermissionMuteDeafen
13 | PermissionMove
14 | PermissionMakeChannel
15 | PermissionLinkChannel
16 | PermissionWhisper
17 | PermissionTextMessage
18 | PermissionMakeTemporaryChannel
19 | )
20 |
21 | // Permissions that can only be applied in the root channel.
22 | const (
23 | PermissionKick Permission = 0x10000 << iota
24 | PermissionBan
25 | PermissionRegister
26 | PermissionRegisterSelf
27 | )
28 |
29 | // Has returns true if the Permission p contains Permission o has part of its
30 | // bitmask.
31 | func (p Permission) Has(o Permission) bool {
32 | return p&o == o
33 | }
34 |
--------------------------------------------------------------------------------
/gumble/users.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | // Users is a map of server users.
4 | //
5 | // When accessed through client.Users, it contains all users currently on the
6 | // server. When accessed through a specific channel
7 | // (e.g. client.Channels[0].Users), it contains only the users in the
8 | // channel.
9 | type Users map[uint32]*User
10 |
11 | // create adds a new user with the given session to the collection. If a user
12 | // with the given session already exists, it is overwritten.
13 | func (u Users) create(session uint32) *User {
14 | user := &User{
15 | Session: session,
16 | }
17 | u[session] = user
18 | return user
19 | }
20 |
21 | // Find returns the user with the given name. nil is returned if no user exists
22 | // with the given name.
23 | func (u Users) Find(name string) *User {
24 | for _, user := range u {
25 | if user.Name == name {
26 | return user
27 | }
28 | }
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/gumble/audiolisteners.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | type audioEventItem struct {
4 | parent *AudioListeners
5 | prev, next *audioEventItem
6 | listener AudioListener
7 | streams map[*User]chan *AudioPacket
8 | }
9 |
10 | func (e *audioEventItem) Detach() {
11 | if e.prev == nil {
12 | e.parent.head = e.next
13 | } else {
14 | e.prev.next = e.next
15 | }
16 | if e.next == nil {
17 | e.parent.tail = e.prev
18 | } else {
19 | e.next.prev = e.prev
20 | }
21 | }
22 |
23 | // AudioListeners is a list of audio listeners. Each attached listener is
24 | // called in sequence when a new user audio stream begins.
25 | type AudioListeners struct {
26 | head, tail *audioEventItem
27 | }
28 |
29 | // Attach adds a new audio listener to the end of the current list of listeners.
30 | func (e *AudioListeners) Attach(listener AudioListener) Detacher {
31 | item := &audioEventItem{
32 | parent: e,
33 | prev: e.tail,
34 | listener: listener,
35 | streams: make(map[*User]chan *AudioPacket),
36 | }
37 | if e.head == nil {
38 | e.head = item
39 | }
40 | if e.tail == nil {
41 | e.tail = item
42 | } else {
43 | e.tail.next = item
44 | }
45 | return item
46 | }
47 |
--------------------------------------------------------------------------------
/gumbleutil/textmessage.go:
--------------------------------------------------------------------------------
1 | package gumbleutil
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "strings"
7 |
8 | "layeh.com/gumble/gumble"
9 | )
10 |
11 | // PlainText returns the Message string without HTML tags or entities.
12 | func PlainText(tm *gumble.TextMessage) string {
13 | d := xml.NewDecoder(strings.NewReader(tm.Message))
14 | d.Strict = false
15 | d.AutoClose = xml.HTMLAutoClose
16 | d.Entity = xml.HTMLEntity
17 |
18 | var b bytes.Buffer
19 | newline := false
20 | for {
21 | t, _ := d.Token()
22 | if t == nil {
23 | break
24 | }
25 | switch node := t.(type) {
26 | case xml.CharData:
27 | if len(node) > 0 {
28 | b.Write(node)
29 | newline = false
30 | }
31 | case xml.StartElement:
32 | switch node.Name.Local {
33 | case "address", "article", "aside", "audio", "blockquote", "canvas", "dd", "div", "dl", "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "noscript", "ol", "output", "p", "pre", "section", "table", "tfoot", "ul", "video":
34 | if !newline {
35 | b.WriteByte('\n')
36 | newline = true
37 | }
38 | case "br":
39 | b.WriteByte('\n')
40 | newline = true
41 | }
42 | }
43 | }
44 | return b.String()
45 | }
46 |
--------------------------------------------------------------------------------
/gumble/textmessage.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "layeh.com/gumble/gumble/MumbleProto"
5 | )
6 |
7 | // TextMessage is a chat message that can be received from and sent to the
8 | // server.
9 | type TextMessage struct {
10 | // User who sent the message (can be nil).
11 | Sender *User
12 | // Users that receive the message.
13 | Users []*User
14 | // Channels that receive the message.
15 | Channels []*Channel
16 | // Channels that receive the message and send it recursively to sub-channels.
17 | Trees []*Channel
18 | // Chat message.
19 | Message string
20 | }
21 |
22 | func (t *TextMessage) writeMessage(client *Client) error {
23 | packet := MumbleProto.TextMessage{
24 | Message: &t.Message,
25 | }
26 | if t.Users != nil {
27 | packet.Session = make([]uint32, len(t.Users))
28 | for i, user := range t.Users {
29 | packet.Session[i] = user.Session
30 | }
31 | }
32 | if t.Channels != nil {
33 | packet.ChannelId = make([]uint32, len(t.Channels))
34 | for i, channel := range t.Channels {
35 | packet.ChannelId[i] = channel.ID
36 | }
37 | }
38 | if t.Trees != nil {
39 | packet.TreeId = make([]uint32, len(t.Trees))
40 | for i, channel := range t.Trees {
41 | packet.TreeId[i] = channel.ID
42 | }
43 | }
44 | return client.Conn.WriteProto(&packet)
45 | }
46 |
--------------------------------------------------------------------------------
/gumble/MumbleProto/generate_main.go:
--------------------------------------------------------------------------------
1 | // +build ignore
2 |
3 | package main
4 |
5 | import (
6 | "io"
7 | "log"
8 | "net/http"
9 | "os"
10 | "os/exec"
11 | )
12 |
13 | func main() {
14 | // Get Mumble.proto
15 | resp, err := http.Get("https://raw.githubusercontent.com/mumble-voip/mumble/master/src/Mumble.proto")
16 | if err != nil {
17 | log.Fatalf("could not download Mumble.proto: %s\n", err)
18 | }
19 | defer resp.Body.Close()
20 |
21 | // Write Mumble.proto
22 | f, err := os.Create("Mumble.proto")
23 | if err != nil {
24 | log.Fatalf("could not create Mumble.proto: %s\n", err)
25 | }
26 | if _, err := io.Copy(f, resp.Body); err != nil {
27 | log.Fatalf("could not write Mumble.proto: %s\n", err)
28 | }
29 | if err := f.Close(); err != nil {
30 | log.Fatalf("could not close Mumble.proto: %s\n", err)
31 | }
32 |
33 | // Build proto-gen-go
34 | if err := exec.Command("go", "build", "-o", "protoc-gen-go", "github.com/golang/protobuf/protoc-gen-go").Run(); err != nil {
35 | log.Fatalf("could not build protoc-gen-go: %s\n", err)
36 | }
37 |
38 | // Generate code
39 | if err := exec.Command("protoc", "--go_out=.", "Mumble.proto").Run(); err != nil {
40 | log.Fatalf("could not run protoc: %s\n", err)
41 | }
42 |
43 | // Clean up
44 | os.Remove("Mumble.proto")
45 | os.Remove("protoc-gen-go")
46 | }
47 |
--------------------------------------------------------------------------------
/_examples/mumble-audio-player/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 |
9 | "layeh.com/gumble/gumble"
10 | "layeh.com/gumble/gumbleffmpeg"
11 | "layeh.com/gumble/gumbleutil"
12 | _ "layeh.com/gumble/opus"
13 | )
14 |
15 | func main() {
16 | files := make(map[string]string)
17 | var stream *gumbleffmpeg.Stream
18 |
19 | flag.Usage = func() {
20 | fmt.Fprintf(os.Stderr, "Usage of %s: [flags] [audio files...]\n", os.Args[0])
21 | flag.PrintDefaults()
22 | }
23 |
24 | gumbleutil.Main(gumbleutil.AutoBitrate, gumbleutil.Listener{
25 | Connect: func(e *gumble.ConnectEvent) {
26 | for _, file := range flag.Args() {
27 | key := filepath.Base(file)
28 | files[key] = file
29 | }
30 |
31 | fmt.Printf("audio player loaded! (%d files)\n", len(files))
32 | },
33 |
34 | TextMessage: func(e *gumble.TextMessageEvent) {
35 | if e.Sender == nil {
36 | return
37 | }
38 | file, ok := files[e.Message]
39 | if !ok {
40 | return
41 | }
42 | if stream != nil && stream.State() == gumbleffmpeg.StatePlaying {
43 | return
44 | }
45 | stream = gumbleffmpeg.New(e.Client, gumbleffmpeg.SourceFile(file))
46 | if err := stream.Play(); err != nil {
47 | fmt.Printf("%s\n", err)
48 | } else {
49 | fmt.Printf("Playing %s\n", file)
50 | }
51 | },
52 | })
53 | }
54 |
--------------------------------------------------------------------------------
/gumble/audiocodec.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | const (
8 | audioCodecIDOpus = 4
9 | )
10 |
11 | var (
12 | audioCodecsLock sync.Mutex
13 | audioCodecs [8]AudioCodec
14 | )
15 |
16 | // RegisterAudioCodec registers an audio codec that can be used for encoding
17 | // and decoding outgoing and incoming audio data. The function panics if the
18 | // ID is invalid.
19 | func RegisterAudioCodec(id int, codec AudioCodec) {
20 | audioCodecsLock.Lock()
21 | defer audioCodecsLock.Unlock()
22 |
23 | if id < 0 || id >= len(audioCodecs) {
24 | panic("id out of range")
25 | }
26 | audioCodecs[id] = codec
27 | }
28 |
29 | func getAudioCodec(id int) AudioCodec {
30 | audioCodecsLock.Lock()
31 | defer audioCodecsLock.Unlock()
32 | return audioCodecs[id]
33 | }
34 |
35 | // AudioCodec can create a encoder and a decoder for outgoing and incoming
36 | // data.
37 | type AudioCodec interface {
38 | ID() int
39 | NewEncoder() AudioEncoder
40 | NewDecoder() AudioDecoder
41 | }
42 |
43 | // AudioEncoder encodes a chunk of PCM audio samples to a certain type.
44 | type AudioEncoder interface {
45 | ID() int
46 | Encode(pcm []int16, mframeSize, maxDataBytes int) ([]byte, error)
47 | Reset()
48 | }
49 |
50 | // AudioDecoder decodes an encoded byte slice to a chunk of PCM audio samples.
51 | type AudioDecoder interface {
52 | ID() int
53 | Decode(data []byte, frameSize int) ([]int16, error)
54 | Reset()
55 | }
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # gumble
4 |
5 | gumble is a [Mumble](https://mumble.info/) client implementation in Go
6 |
7 | ## Sub-projects
8 |
9 | - gumble ([docs](https://pkg.go.dev/layeh.com/gumble/gumble))
10 | - Client library
11 | - gumbleopenal ([docs](https://pkg.go.dev/layeh.com/gumble/gumbleopenal))
12 | - [OpenAL](http://kcat.strangesoft.net/openal.html) audio system for gumble
13 | - gumbleffmpeg ([docs](https://pkg.go.dev/layeh.com/gumble/gumbleffmpeg))
14 | - [ffmpeg](https://www.ffmpeg.org/) audio source for gumble
15 | - gumbleutil ([docs](https://pkg.go.dev/layeh.com/gumble/gumbleutil))
16 | - Extras that can make working with gumble easier
17 |
18 | ## Example
19 |
20 | ```go
21 | package main
22 |
23 | import (
24 | "layeh.com/gumble/gumble"
25 | "layeh.com/gumble/gumbleutil"
26 | )
27 |
28 | func main() {
29 | gumbleutil.Main(gumbleutil.Listener{
30 | UserChange: func(e *gumble.UserChangeEvent) {
31 | if e.Type.Has(gumble.UserChangeConnected) {
32 | e.User.Send("Welcome to the server, " + e.User.Name + "!")
33 | }
34 | },
35 | })
36 | }
37 | ```
38 |
39 | ## Related projects
40 |
41 | - [barnard](https://layeh.com/barnard)
42 | - terminal-based Mumble client
43 | - [piepan](https://layeh.com/piepan)
44 | - an easy to use framework for writing Mumble bots using Lua
45 |
46 | ## License
47 |
48 | MPL 2.0
49 |
50 | ## Author
51 |
52 | Tim Cooper ()
53 |
--------------------------------------------------------------------------------
/opus/opus.go:
--------------------------------------------------------------------------------
1 | package opus
2 |
3 | import (
4 | "layeh.com/gopus"
5 | "layeh.com/gumble/gumble"
6 | )
7 |
8 | var Codec gumble.AudioCodec
9 |
10 | const ID = 4
11 |
12 | func init() {
13 | Codec = &generator{}
14 | gumble.RegisterAudioCodec(4, Codec)
15 | }
16 |
17 | // generator
18 |
19 | type generator struct {
20 | }
21 |
22 | func (*generator) ID() int {
23 | return ID
24 | }
25 |
26 | func (*generator) NewEncoder() gumble.AudioEncoder {
27 | e, _ := gopus.NewEncoder(gumble.AudioSampleRate, gumble.AudioChannels, gopus.Voip)
28 | e.SetBitrate(gopus.BitrateMaximum)
29 | return &Encoder{
30 | e,
31 | }
32 | }
33 |
34 | func (*generator) NewDecoder() gumble.AudioDecoder {
35 | d, _ := gopus.NewDecoder(gumble.AudioSampleRate, gumble.AudioChannels)
36 | return &Decoder{
37 | d,
38 | }
39 | }
40 |
41 | // encoder
42 |
43 | type Encoder struct {
44 | *gopus.Encoder
45 | }
46 |
47 | func (*Encoder) ID() int {
48 | return ID
49 | }
50 |
51 | func (e *Encoder) Encode(pcm []int16, mframeSize, maxDataBytes int) ([]byte, error) {
52 | return e.Encoder.Encode(pcm, mframeSize, maxDataBytes)
53 | }
54 |
55 | func (e *Encoder) Reset() {
56 | e.Encoder.ResetState()
57 | }
58 |
59 | // decoder
60 |
61 | type Decoder struct {
62 | *gopus.Decoder
63 | }
64 |
65 | func (*Decoder) ID() int {
66 | return 4
67 | }
68 |
69 | func (d *Decoder) Decode(data []byte, frameSize int) ([]int16, error) {
70 | return d.Decoder.Decode(data, frameSize, false)
71 | }
72 |
73 | func (d *Decoder) Reset() {
74 | d.Decoder.ResetState()
75 | }
76 |
--------------------------------------------------------------------------------
/gumble/doc.go:
--------------------------------------------------------------------------------
1 | // Package gumble is a client for the Mumble voice chat software.
2 | //
3 | // Getting started
4 | //
5 | // 1. Create a new Config to hold your connection settings:
6 | //
7 | // config := gumble.NewConfig()
8 | // config.Username = "gumble-test"
9 | //
10 | // 2. Attach event listeners to the configuration:
11 | //
12 | // config.Attach(gumbleutil.Listener{
13 | // TextMessage: func(e *gumble.TextMessageEvent) {
14 | // fmt.Printf("Received text message: %s\n", e.Message)
15 | // },
16 | // })
17 | //
18 | // 3. Connect to the server:
19 | //
20 | // client, err := gumble.Dial("example.com:64738", config)
21 | // if err != nil {
22 | // panic(err)
23 | // }
24 | //
25 | // Audio codecs
26 | //
27 | // Currently, only the Opus codec (https://www.opus-codec.org/) is supported
28 | // for transmitting and receiving audio. It can be enabled by importing the
29 | // following package for its side effect:
30 | // import (
31 | // _ "layeh.com/gumble/opus"
32 | // )
33 | //
34 | // To ensure that gumble clients can always transmit and receive audio to and
35 | // from your server, add the following line to your murmur configuration file:
36 | //
37 | // opusthreshold=0
38 | //
39 | // Thread safety
40 | //
41 | // As a general rule, a Client everything that is associated with it
42 | // (Users, Channels, Config, etc.), is thread-unsafe. Accessing or modifying
43 | // those structures should only be done from inside of an event listener or via
44 | // Client.Do.
45 | package gumble
46 |
--------------------------------------------------------------------------------
/gumble/varint/read.go:
--------------------------------------------------------------------------------
1 | package varint
2 |
3 | import (
4 | "encoding/binary"
5 | )
6 |
7 | // Decode reads the first varint encoded number from the given buffer.
8 | //
9 | // On success, the function returns the varint as an int64, and the number of
10 | // bytes read (0 if there was an error).
11 | func Decode(b []byte) (int64, int) {
12 | if len(b) == 0 {
13 | return 0, 0
14 | }
15 | // 0xxxxxxx 7-bit positive number
16 | if (b[0] & 0x80) == 0 {
17 | return int64(b[0]), 1
18 | }
19 | // 10xxxxxx + 1 byte 14-bit positive number
20 | if (b[0]&0xC0) == 0x80 && len(b) >= 2 {
21 | return int64(b[0]&0x3F)<<8 | int64(b[1]), 2
22 | }
23 | // 110xxxxx + 2 bytes 21-bit positive number
24 | if (b[0]&0xE0) == 0xC0 && len(b) >= 3 {
25 | return int64(b[0]&0x1F)<<16 | int64(b[1])<<8 | int64(b[2]), 3
26 | }
27 | // 1110xxxx + 3 bytes 28-bit positive number
28 | if (b[0]&0xF0) == 0xE0 && len(b) >= 4 {
29 | return int64(b[0]&0xF)<<24 | int64(b[1])<<16 | int64(b[2])<<8 | int64(b[3]), 4
30 | }
31 | // 111100__ + int (32-bit) 32-bit positive number
32 | if (b[0]&0xFC) == 0xF0 && len(b) >= 5 {
33 | return int64(binary.BigEndian.Uint32(b[1:])), 5
34 | }
35 | // 111101__ + long (64-bit) 64-bit number
36 | if (b[0]&0xFC) == 0xF4 && len(b) >= 9 {
37 | return int64(binary.BigEndian.Uint64(b[1:])), 9
38 | }
39 | // 111110__ + varint Negative recursive varint
40 | if b[0]&0xFC == 0xF8 {
41 | if v, n := Decode(b[1:]); n > 0 {
42 | return -v, n + 1
43 | }
44 | return 0, 0
45 | }
46 | // 111111xx Byte-inverted negative two bit number (~xx)
47 | if b[0]&0xFC == 0xFC {
48 | return ^int64(b[0] & 0x03), 1
49 | }
50 |
51 | return 0, 0
52 | }
53 |
--------------------------------------------------------------------------------
/gumbleutil/acl.go:
--------------------------------------------------------------------------------
1 | package gumbleutil
2 |
3 | import (
4 | "layeh.com/gumble/gumble"
5 | )
6 |
7 | // UserGroups fetches the group names the given user belongs to in the given
8 | // channel. The slice of group names sent via the returned channel. On error,
9 | // the returned channel is closed without without sending a slice.
10 | func UserGroups(client *gumble.Client, user *gumble.User, channel *gumble.Channel) <-chan []string {
11 | ch := make(chan []string)
12 |
13 | if !user.IsRegistered() {
14 | close(ch)
15 | return ch
16 | }
17 |
18 | var detacher gumble.Detacher
19 | listener := Listener{
20 | Disconnect: func(e *gumble.DisconnectEvent) {
21 | detacher.Detach()
22 | close(ch)
23 | },
24 | ChannelChange: func(e *gumble.ChannelChangeEvent) {
25 | if e.Channel == channel && e.Type.Has(gumble.ChannelChangeRemoved) {
26 | detacher.Detach()
27 | close(ch)
28 | }
29 | },
30 | PermissionDenied: func(e *gumble.PermissionDeniedEvent) {
31 | if e.Channel == channel && e.Type == gumble.PermissionDeniedPermission && (e.Permission&gumble.PermissionWrite) != 0 {
32 | detacher.Detach()
33 | close(ch)
34 | }
35 | },
36 | ACL: func(e *gumble.ACLEvent) {
37 | if e.ACL.Channel != channel {
38 | return
39 | }
40 | var names []string
41 | for _, g := range e.ACL.Groups {
42 | if (g.UsersAdd[user.UserID] != nil || g.UsersInherited[user.UserID] != nil) && g.UsersRemove[user.UserID] == nil {
43 | names = append(names, g.Name)
44 | }
45 | }
46 | detacher.Detach()
47 | ch <- names
48 | close(ch)
49 | },
50 | }
51 | detacher = client.Config.Attach(&listener)
52 | channel.RequestACL()
53 |
54 | return ch
55 | }
56 |
--------------------------------------------------------------------------------
/gumble/userstats.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "crypto/x509"
5 | "net"
6 | "time"
7 | )
8 |
9 | // UserStats contains additional information about a user.
10 | type UserStats struct {
11 | // The owner of the stats.
12 | User *User
13 |
14 | // Stats about UDP packets sent from the client.
15 | FromClient UserStatsUDP
16 | // Stats about UDP packets sent by the server.
17 | FromServer UserStatsUDP
18 |
19 | // Number of UDP packets sent by the user.
20 | UDPPackets uint32
21 | // Average UDP ping.
22 | UDPPingAverage float32
23 | // UDP ping variance.
24 | UDPPingVariance float32
25 |
26 | // Number of TCP packets sent by the user.
27 | TCPPackets uint32
28 | // Average TCP ping.
29 | TCPPingAverage float32
30 | // TCP ping variance.
31 | TCPPingVariance float32
32 |
33 | // The user's version.
34 | Version Version
35 | // When the user connected to the server.
36 | Connected time.Time
37 | // How long the user has been idle.
38 | Idle time.Duration
39 | // How much bandwidth the user is current using.
40 | Bandwidth int
41 | // The user's certificate chain.
42 | Certificates []*x509.Certificate
43 | // Does the user have a strong certificate? A strong certificate is one that
44 | // is not self signed, nor expired, etc.
45 | StrongCertificate bool
46 | // A list of CELT versions supported by the user's client.
47 | CELTVersions []int32
48 | // Does the user's client supports the Opus audio codec?
49 | Opus bool
50 |
51 | // The user's IP address.
52 | IP net.IP
53 | }
54 |
55 | // UserStatsUDP contains stats about UDP packets that have been sent to or from
56 | // the server.
57 | type UserStatsUDP struct {
58 | Good uint32
59 | Late uint32
60 | Lost uint32
61 | Resync uint32
62 | }
63 |
--------------------------------------------------------------------------------
/gumble/varint/write.go:
--------------------------------------------------------------------------------
1 | package varint
2 |
3 | import (
4 | "encoding/binary"
5 | "math"
6 | )
7 |
8 | // MaxVarintLen is the maximum number of bytes required to encode a varint
9 | // number.
10 | const MaxVarintLen = 10
11 |
12 | // Encode encodes the given value to varint format.
13 | func Encode(b []byte, value int64) int {
14 | // 111111xx Byte-inverted negative two bit number (~xx)
15 | if value <= -1 && value >= -4 {
16 | b[0] = 0xFC | byte(^value&0xFF)
17 | return 1
18 | }
19 | // 111110__ + varint Negative recursive varint
20 | if value < 0 {
21 | b[0] = 0xF8
22 | return 1 + Encode(b[1:], -value)
23 | }
24 | // 0xxxxxxx 7-bit positive number
25 | if value <= 0x7F {
26 | b[0] = byte(value)
27 | return 1
28 | }
29 | // 10xxxxxx + 1 byte 14-bit positive number
30 | if value <= 0x3FFF {
31 | b[0] = byte(((value >> 8) & 0x3F) | 0x80)
32 | b[1] = byte(value & 0xFF)
33 | return 2
34 | }
35 | // 110xxxxx + 2 bytes 21-bit positive number
36 | if value <= 0x1FFFFF {
37 | b[0] = byte((value>>16)&0x1F | 0xC0)
38 | b[1] = byte((value >> 8) & 0xFF)
39 | b[2] = byte(value & 0xFF)
40 | return 3
41 | }
42 | // 1110xxxx + 3 bytes 28-bit positive number
43 | if value <= 0xFFFFFFF {
44 | b[0] = byte((value>>24)&0xF | 0xE0)
45 | b[1] = byte((value >> 16) & 0xFF)
46 | b[2] = byte((value >> 8) & 0xFF)
47 | b[3] = byte(value & 0xFF)
48 | return 4
49 | }
50 | // 111100__ + int (32-bit) 32-bit positive number
51 | if value <= math.MaxInt32 {
52 | b[0] = 0xF0
53 | binary.BigEndian.PutUint32(b[1:], uint32(value))
54 | return 5
55 | }
56 | // 111101__ + long (64-bit) 64-bit number
57 | if value <= math.MaxInt64 {
58 | b[0] = 0xF4
59 | binary.BigEndian.PutUint64(b[1:], uint64(value))
60 | return 9
61 | }
62 |
63 | return 0
64 | }
65 |
--------------------------------------------------------------------------------
/gumble/config.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // Config holds the Mumble configuration used by Client. A single Config should
8 | // not be shared between multiple Client instances.
9 | type Config struct {
10 | // User name used when authenticating with the server.
11 | Username string
12 | // Password used when authenticating with the server. A password is not
13 | // usually required to connect to a server.
14 | Password string
15 | // The initial access tokens to the send to the server. Access tokens can be
16 | // resent to the server using:
17 | // client.Send(config.Tokens)
18 | Tokens AccessTokens
19 |
20 | // AudioInterval is the interval at which audio packets are sent. Valid
21 | // values are: 10ms, 20ms, 40ms, and 60ms.
22 | AudioInterval time.Duration
23 | // AudioDataBytes is the number of bytes that an audio frame can use.
24 | AudioDataBytes int
25 |
26 | // The event listeners used when client events are triggered.
27 | Listeners Listeners
28 | AudioListeners AudioListeners
29 | }
30 |
31 | // NewConfig returns a new Config struct with default values set.
32 | func NewConfig() *Config {
33 | return &Config{
34 | AudioInterval: AudioDefaultInterval,
35 | AudioDataBytes: AudioDefaultDataBytes,
36 | }
37 | }
38 |
39 | // Attach is an alias of c.Listeners.Attach.
40 | func (c *Config) Attach(l EventListener) Detacher {
41 | return c.Listeners.Attach(l)
42 | }
43 |
44 | // AttachAudio is an alias of c.AudioListeners.Attach.
45 | func (c *Config) AttachAudio(l AudioListener) Detacher {
46 | return c.AudioListeners.Attach(l)
47 | }
48 |
49 | // AudioFrameSize returns the appropriate audio frame size, based off of the
50 | // audio interval.
51 | func (c *Config) AudioFrameSize() int {
52 | return int(c.AudioInterval/AudioDefaultInterval) * AudioDefaultFrameSize
53 | }
54 |
--------------------------------------------------------------------------------
/gumble/contextaction.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "layeh.com/gumble/gumble/MumbleProto"
5 | )
6 |
7 | // ContextActionType is a bitmask of contexts where a ContextAction can be
8 | // triggered.
9 | type ContextActionType int
10 |
11 | // Supported ContextAction contexts.
12 | const (
13 | ContextActionServer ContextActionType = ContextActionType(MumbleProto.ContextActionModify_Server)
14 | ContextActionChannel ContextActionType = ContextActionType(MumbleProto.ContextActionModify_Channel)
15 | ContextActionUser ContextActionType = ContextActionType(MumbleProto.ContextActionModify_User)
16 | )
17 |
18 | // ContextAction is an triggerable item that has been added by a server-side
19 | // plugin.
20 | type ContextAction struct {
21 | // The context action type.
22 | Type ContextActionType
23 | // The name of the context action.
24 | Name string
25 | // The user-friendly description of the context action.
26 | Label string
27 |
28 | client *Client
29 | }
30 |
31 | // Trigger will trigger the context action in the context of the server.
32 | func (c *ContextAction) Trigger() {
33 | packet := MumbleProto.ContextAction{
34 | Action: &c.Name,
35 | }
36 | c.client.Conn.WriteProto(&packet)
37 | }
38 |
39 | // TriggerUser will trigger the context action in the context of the given
40 | // user.
41 | func (c *ContextAction) TriggerUser(user *User) {
42 | packet := MumbleProto.ContextAction{
43 | Session: &user.Session,
44 | Action: &c.Name,
45 | }
46 | c.client.Conn.WriteProto(&packet)
47 | }
48 |
49 | // TriggerChannel will trigger the context action in the context of the given
50 | // channel.
51 | func (c *ContextAction) TriggerChannel(channel *Channel) {
52 | packet := MumbleProto.ContextAction{
53 | ChannelId: &channel.ID,
54 | Action: &c.Name,
55 | }
56 | c.client.Conn.WriteProto(&packet)
57 | }
58 |
--------------------------------------------------------------------------------
/cmd/mumble-ping/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "flag"
6 | "fmt"
7 | "net"
8 | "os"
9 | "strconv"
10 | "time"
11 |
12 | "layeh.com/gumble/gumble"
13 | )
14 |
15 | func main() {
16 | flag.Usage = func() {
17 | fmt.Fprintf(os.Stderr, "usage: %s [flags] \n", os.Args[0])
18 | flag.PrintDefaults()
19 | }
20 | interval := flag.Duration("interval", time.Second*1, "ping packet retransmission interval")
21 | timeout := flag.Duration("timeout", time.Second*5, "ping timeout until failure")
22 | jsonOutput := flag.Bool("json", false, "output success response as JSON")
23 | flag.Parse()
24 | if flag.NArg() != 1 {
25 | flag.Usage()
26 | os.Exit(1)
27 | }
28 |
29 | server := flag.Arg(0)
30 |
31 | host, port, err := net.SplitHostPort(server)
32 | if err != nil {
33 | host = server
34 | port = strconv.Itoa(gumble.DefaultPort)
35 | }
36 |
37 | resp, err := gumble.Ping(net.JoinHostPort(host, port), *interval, *timeout)
38 | if err != nil {
39 | fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err)
40 | os.Exit(1)
41 | }
42 | major, minor, patch := resp.Version.SemanticVersion()
43 |
44 | if !*jsonOutput {
45 | fmt.Printf("Address: %s\n", resp.Address)
46 | fmt.Printf("Ping: %s\n", resp.Ping)
47 | fmt.Printf("Version: %d.%d.%d\n", major, minor, patch)
48 | fmt.Printf("Connected Users: %d\n", resp.ConnectedUsers)
49 | fmt.Printf("Maximum Users: %d\n", resp.MaximumUsers)
50 | fmt.Printf("Maximum Bitrate: %d\n", resp.MaximumBitrate)
51 | } else {
52 | output := map[string]interface{}{
53 | "address": resp.Address.String(),
54 | "ping": float64(resp.Ping) / float64(time.Millisecond),
55 | "version": fmt.Sprintf("%d.%d.%d", major, minor, patch),
56 | "connected_users": resp.ConnectedUsers,
57 | "maximum_users": resp.MaximumUsers,
58 | "maximum_bitrate": resp.MaximumBitrate,
59 | }
60 | encoder := json.NewEncoder(os.Stdout)
61 | encoder.Encode(output)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/gumble/userlist.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "time"
5 |
6 | "layeh.com/gumble/gumble/MumbleProto"
7 | )
8 |
9 | // RegisteredUser represents a registered user on the server.
10 | type RegisteredUser struct {
11 | // The registered user's ID.
12 | UserID uint32
13 | // The registered user's name.
14 | Name string
15 | // The last time the user was seen by the server.
16 | LastSeen time.Time
17 | // The last channel the user was seen in.
18 | LastChannel *Channel
19 |
20 | changed bool
21 | deregister bool
22 | }
23 |
24 | // SetName sets the new name for the user.
25 | func (r *RegisteredUser) SetName(name string) {
26 | r.Name = name
27 | r.changed = true
28 | }
29 |
30 | // Deregister will remove the registered user from the server.
31 | func (r *RegisteredUser) Deregister() {
32 | r.deregister = true
33 | }
34 |
35 | // Register will keep the user registered on the server. This is only useful if
36 | // Deregister() was called on the registered user.
37 | func (r *RegisteredUser) Register() {
38 | r.deregister = false
39 | }
40 |
41 | // ACLUser returns an ACLUser for the given registered user.
42 | func (r *RegisteredUser) ACLUser() *ACLUser {
43 | return &ACLUser{
44 | UserID: r.UserID,
45 | Name: r.Name,
46 | }
47 | }
48 |
49 | // RegisteredUsers is a list of users who are registered on the server.
50 | //
51 | // Whenever a registered user is changed, it does not come into effect until
52 | // the registered user list is sent back to the server.
53 | type RegisteredUsers []*RegisteredUser
54 |
55 | func (r RegisteredUsers) writeMessage(client *Client) error {
56 | packet := MumbleProto.UserList{}
57 |
58 | for _, user := range r {
59 | if user.deregister || user.changed {
60 | userListUser := &MumbleProto.UserList_User{
61 | UserId: &user.UserID,
62 | }
63 | if !user.deregister {
64 | userListUser.Name = &user.Name
65 | }
66 | packet.Users = append(packet.Users, userListUser)
67 | }
68 | }
69 |
70 | if len(packet.Users) <= 0 {
71 | return nil
72 | }
73 | return client.Conn.WriteProto(&packet)
74 | }
75 |
--------------------------------------------------------------------------------
/gumble/reject.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "strconv"
5 |
6 | "layeh.com/gumble/gumble/MumbleProto"
7 | )
8 |
9 | // RejectType describes why a client connection was rejected by the server.
10 | type RejectType int
11 |
12 | // The possible reason why a client connection was rejected by the server.
13 | const (
14 | RejectNone RejectType = RejectType(MumbleProto.Reject_None)
15 | RejectVersion RejectType = RejectType(MumbleProto.Reject_WrongVersion)
16 | RejectUserName RejectType = RejectType(MumbleProto.Reject_InvalidUsername)
17 | RejectUserCredentials RejectType = RejectType(MumbleProto.Reject_WrongUserPW)
18 | RejectServerPassword RejectType = RejectType(MumbleProto.Reject_WrongServerPW)
19 | RejectUsernameInUse RejectType = RejectType(MumbleProto.Reject_UsernameInUse)
20 | RejectServerFull RejectType = RejectType(MumbleProto.Reject_ServerFull)
21 | RejectNoCertificate RejectType = RejectType(MumbleProto.Reject_NoCertificate)
22 | RejectAuthenticatorFail RejectType = RejectType(MumbleProto.Reject_AuthenticatorFail)
23 | )
24 |
25 | // RejectError is returned by DialWithDialer when the server rejects the client
26 | // connection.
27 | type RejectError struct {
28 | Type RejectType
29 | Reason string
30 | }
31 |
32 | // Error implements error.
33 | func (e RejectError) Error() string {
34 | var msg string
35 | switch e.Type {
36 | case RejectNone:
37 | msg = "none"
38 | case RejectVersion:
39 | msg = "wrong client version"
40 | case RejectUserName:
41 | msg = "invalid username"
42 | case RejectUserCredentials:
43 | msg = "incorrect user credentials"
44 | case RejectServerPassword:
45 | msg = "incorrect server password"
46 | case RejectUsernameInUse:
47 | msg = "username in use"
48 | case RejectServerFull:
49 | msg = "server full"
50 | case RejectNoCertificate:
51 | msg = "no certificate"
52 | case RejectAuthenticatorFail:
53 | msg = "authenticator fail"
54 | default:
55 | msg = "unknown type " + strconv.Itoa(int(e.Type))
56 | }
57 | if e.Reason != "" {
58 | msg += ": " + e.Reason
59 | }
60 | return msg
61 | }
62 |
--------------------------------------------------------------------------------
/gumble/MumbleProto/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2005-2013, Thorvald Natvig
2 | Copyright (C) 2007, Stefan Gehn
3 | Copyright (C) 2007, Sebastian Schlingmann
4 | Copyright (C) 2008-2013, Mikkel Krautz
5 | Copyright (C) 2008, Andreas Messer
6 | Copyright (C) 2007, Trenton Schulz
7 | Copyright (C) 2008-2013, Stefan Hacker
8 | Copyright (C) 2008-2011, Snares
9 | Copyright (C) 2009-2013, Benjamin Jemlich
10 | Copyright (C) 2009-2013, Kissaki
11 |
12 | All rights reserved.
13 |
14 | Redistribution and use in source and binary forms, with or without
15 | modification, are permitted provided that the following conditions
16 | are met:
17 |
18 | - Redistributions of source code must retain the above copyright notice,
19 | this list of conditions and the following disclaimer.
20 | - Redistributions in binary form must reproduce the above copyright notice,
21 | this list of conditions and the following disclaimer in the documentation
22 | and/or other materials provided with the distribution.
23 | - Neither the name of the Mumble Developers nor the names of its
24 | contributors may be used to endorse or promote products derived from this
25 | software without specific prior written permission.
26 |
27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 | `AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
31 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
34 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 |
--------------------------------------------------------------------------------
/gumbleffmpeg/source.go:
--------------------------------------------------------------------------------
1 | package gumbleffmpeg
2 |
3 | import (
4 | "io"
5 | "os/exec"
6 | )
7 |
8 | // Source is a Stream source.
9 | type Source interface {
10 | // must include the -i
11 | arguments() []string
12 | start(*exec.Cmd) error
13 | done()
14 | }
15 |
16 | // sourceFile
17 |
18 | type sourceFile string
19 |
20 | // SourceFile is standard file source.
21 | func SourceFile(filename string) Source {
22 | return sourceFile(filename)
23 | }
24 |
25 | func (s sourceFile) arguments() []string {
26 | return []string{"-i", string(s)}
27 | }
28 |
29 | func (sourceFile) start(*exec.Cmd) error {
30 | return nil
31 | }
32 |
33 | func (sourceFile) done() {
34 | }
35 |
36 | // sourceReader
37 |
38 | type sourceReader struct {
39 | r io.ReadCloser
40 | }
41 |
42 | // SourceReader is a ReadCloser source.
43 | func SourceReader(r io.ReadCloser) Source {
44 | return &sourceReader{r}
45 | }
46 |
47 | func (*sourceReader) arguments() []string {
48 | return []string{"-i", "-"}
49 | }
50 |
51 | func (s *sourceReader) start(cmd *exec.Cmd) error {
52 | cmd.Stdin = s.r
53 | return nil
54 | }
55 |
56 | func (s *sourceReader) done() {
57 | s.r.Close()
58 | }
59 |
60 | // sourceExec
61 |
62 | type sourceExec struct {
63 | name string
64 | arg []string
65 |
66 | cmd *exec.Cmd
67 | }
68 |
69 | // SourceExec uses the output of the given command and arguments as source
70 | // data.
71 | func SourceExec(name string, arg ...string) Source {
72 | return &sourceExec{
73 | name: name,
74 | arg: arg,
75 | }
76 | }
77 |
78 | func (*sourceExec) arguments() []string {
79 | return []string{"-i", "-"}
80 | }
81 |
82 | func (s *sourceExec) start(cmd *exec.Cmd) error {
83 | s.cmd = exec.Command(s.name, s.arg...)
84 | r, err := s.cmd.StdoutPipe()
85 | if err != nil {
86 | return err
87 | }
88 | cmd.Stdin = r
89 | if err := s.cmd.Start(); err != nil {
90 | cmd.Stdin = nil
91 | return err
92 | }
93 | return nil
94 | }
95 |
96 | func (s *sourceExec) done() {
97 | if s.cmd != nil {
98 | if p := s.cmd.Process; p != nil {
99 | p.Kill()
100 | }
101 | s.cmd.Wait()
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/gumbleutil/main.go:
--------------------------------------------------------------------------------
1 | package gumbleutil
2 |
3 | import (
4 | "crypto/tls"
5 | "flag"
6 | "fmt"
7 | "net"
8 | "os"
9 | "strconv"
10 |
11 | "layeh.com/gumble/gumble"
12 | )
13 |
14 | // Main aids in the creation of a basic command line gumble bot. It accepts the
15 | // following flag arguments:
16 | // --server
17 | // --username
18 | // --password
19 | // --insecure
20 | // --certificate
21 | // --key
22 | func Main(listeners ...gumble.EventListener) {
23 | server := flag.String("server", "localhost:64738", "Mumble server address")
24 | username := flag.String("username", "gumble-bot", "client username")
25 | password := flag.String("password", "", "client password")
26 | insecure := flag.Bool("insecure", false, "skip server certificate verification")
27 | certificateFile := flag.String("certificate", "", "user certificate file (PEM)")
28 | keyFile := flag.String("key", "", "user certificate key file (PEM)")
29 |
30 | if !flag.Parsed() {
31 | flag.Parse()
32 | }
33 |
34 | host, port, err := net.SplitHostPort(*server)
35 | if err != nil {
36 | host = *server
37 | port = strconv.Itoa(gumble.DefaultPort)
38 | }
39 |
40 | keepAlive := make(chan bool)
41 |
42 | config := gumble.NewConfig()
43 | config.Username = *username
44 | config.Password = *password
45 | address := net.JoinHostPort(host, port)
46 |
47 | var tlsConfig tls.Config
48 |
49 | if *insecure {
50 | tlsConfig.InsecureSkipVerify = true
51 | }
52 | if *certificateFile != "" {
53 | if *keyFile == "" {
54 | keyFile = certificateFile
55 | }
56 | if certificate, err := tls.LoadX509KeyPair(*certificateFile, *keyFile); err != nil {
57 | fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err)
58 | os.Exit(1)
59 | } else {
60 | tlsConfig.Certificates = append(tlsConfig.Certificates, certificate)
61 | }
62 | }
63 | config.Attach(AutoBitrate)
64 | for _, listener := range listeners {
65 | config.Attach(listener)
66 | }
67 | config.Attach(Listener{
68 | Disconnect: func(e *gumble.DisconnectEvent) {
69 | keepAlive <- true
70 | },
71 | })
72 | _, err = gumble.DialWithDialer(new(net.Dialer), address, config, &tlsConfig)
73 | if err != nil {
74 | fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err)
75 | os.Exit(1)
76 | }
77 |
78 | <-keepAlive
79 | }
80 |
--------------------------------------------------------------------------------
/gumble/voicetarget.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "layeh.com/gumble/gumble/MumbleProto"
5 | )
6 |
7 | // VoiceTargetLoopback is a special voice target which causes any audio sent to
8 | // the server to be returned to the client.
9 | //
10 | // Its ID should not be modified, and it does not have to to be sent to the
11 | // server before use.
12 | var VoiceTargetLoopback *VoiceTarget = &VoiceTarget{
13 | ID: 31,
14 | }
15 |
16 | type voiceTargetChannel struct {
17 | channel *Channel
18 | links, recursive bool
19 | group string
20 | }
21 |
22 | // VoiceTarget represents a set of users and/or channels that the client can
23 | // whisper to.
24 | type VoiceTarget struct {
25 | // The voice target ID. This value must be in the range [1, 30].
26 | ID uint32
27 | users []*User
28 | channels []*voiceTargetChannel
29 | }
30 |
31 | // Clear removes all users and channels from the voice target.
32 | func (v *VoiceTarget) Clear() {
33 | v.users = nil
34 | v.channels = nil
35 | }
36 |
37 | // AddUser adds a user to the voice target.
38 | func (v *VoiceTarget) AddUser(user *User) {
39 | v.users = append(v.users, user)
40 | }
41 |
42 | // AddChannel adds a user to the voice target. If group is non-empty, only
43 | // users belonging to that ACL group will be targeted.
44 | func (v *VoiceTarget) AddChannel(channel *Channel, recursive, links bool, group string) {
45 | v.channels = append(v.channels, &voiceTargetChannel{
46 | channel: channel,
47 | links: links,
48 | recursive: recursive,
49 | group: group,
50 | })
51 | }
52 |
53 | func (v *VoiceTarget) writeMessage(client *Client) error {
54 | packet := MumbleProto.VoiceTarget{
55 | Id: &v.ID,
56 | Targets: make([]*MumbleProto.VoiceTarget_Target, 0, len(v.users)+len(v.channels)),
57 | }
58 | for _, user := range v.users {
59 | packet.Targets = append(packet.Targets, &MumbleProto.VoiceTarget_Target{
60 | Session: []uint32{user.Session},
61 | })
62 | }
63 | for _, vtChannel := range v.channels {
64 | target := &MumbleProto.VoiceTarget_Target{
65 | ChannelId: &vtChannel.channel.ID,
66 | Links: &vtChannel.links,
67 | Children: &vtChannel.recursive,
68 | }
69 | if vtChannel.group != "" {
70 | target.Group = &vtChannel.group
71 | }
72 | packet.Targets = append(packet.Targets, target)
73 | }
74 |
75 | return client.Conn.WriteProto(&packet)
76 | }
77 |
--------------------------------------------------------------------------------
/gumbleutil/listenerfunc.go:
--------------------------------------------------------------------------------
1 | package gumbleutil
2 |
3 | import (
4 | "layeh.com/gumble/gumble"
5 | )
6 |
7 | // ListenerFunc is a single listener function that implements the
8 | // gumble.EventListener interface. This is useful if you would like to use a
9 | // type-switch for handling the different event types.
10 | //
11 | // Example:
12 | // handler := func(e interface{}) {
13 | // switch e.(type) {
14 | // case *gumble.ConnectEvent:
15 | // println("Connected")
16 | // case *gumble.DisconnectEvent:
17 | // println("Disconnected")
18 | // // ...
19 | // }
20 | // }
21 | //
22 | // client.Attach(gumbleutil.ListenerFunc(handler))
23 | type ListenerFunc func(e interface{})
24 |
25 | var _ gumble.EventListener = ListenerFunc(nil)
26 |
27 | // OnConnect implements gumble.EventListener.OnConnect.
28 | func (lf ListenerFunc) OnConnect(e *gumble.ConnectEvent) {
29 | lf(e)
30 | }
31 |
32 | // OnDisconnect implements gumble.EventListener.OnDisconnect.
33 | func (lf ListenerFunc) OnDisconnect(e *gumble.DisconnectEvent) {
34 | lf(e)
35 | }
36 |
37 | // OnTextMessage implements gumble.EventListener.OnTextMessage.
38 | func (lf ListenerFunc) OnTextMessage(e *gumble.TextMessageEvent) {
39 | lf(e)
40 | }
41 |
42 | // OnUserChange implements gumble.EventListener.OnUserChange.
43 | func (lf ListenerFunc) OnUserChange(e *gumble.UserChangeEvent) {
44 | lf(e)
45 | }
46 |
47 | // OnChannelChange implements gumble.EventListener.OnChannelChange.
48 | func (lf ListenerFunc) OnChannelChange(e *gumble.ChannelChangeEvent) {
49 | lf(e)
50 | }
51 |
52 | // OnPermissionDenied implements gumble.EventListener.OnPermissionDenied.
53 | func (lf ListenerFunc) OnPermissionDenied(e *gumble.PermissionDeniedEvent) {
54 | lf(e)
55 | }
56 |
57 | // OnUserList implements gumble.EventListener.OnUserList.
58 | func (lf ListenerFunc) OnUserList(e *gumble.UserListEvent) {
59 | lf(e)
60 | }
61 |
62 | // OnACL implements gumble.EventListener.OnACL.
63 | func (lf ListenerFunc) OnACL(e *gumble.ACLEvent) {
64 | lf(e)
65 | }
66 |
67 | // OnBanList implements gumble.EventListener.OnBanList.
68 | func (lf ListenerFunc) OnBanList(e *gumble.BanListEvent) {
69 | lf(e)
70 | }
71 |
72 | // OnContextActionChange implements gumble.EventListener.OnContextActionChange.
73 | func (lf ListenerFunc) OnContextActionChange(e *gumble.ContextActionChangeEvent) {
74 | lf(e)
75 | }
76 |
77 | // OnServerConfig implements gumble.EventListener.OnServerConfig.
78 | func (lf ListenerFunc) OnServerConfig(e *gumble.ServerConfigEvent) {
79 | lf(e)
80 | }
81 |
--------------------------------------------------------------------------------
/gumble/audio.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | const (
8 | // AudioSampleRate is the audio sample rate (in hertz) for incoming and
9 | // outgoing audio.
10 | AudioSampleRate = 48000
11 |
12 | // AudioDefaultInterval is the default interval that audio packets are sent
13 | // at.
14 | AudioDefaultInterval = 10 * time.Millisecond
15 |
16 | // AudioDefaultFrameSize is the number of audio frames that should be sent in
17 | // a 10ms window.
18 | AudioDefaultFrameSize = AudioSampleRate / 100
19 |
20 | // AudioMaximumFrameSize is the maximum audio frame size from another user
21 | // that will be processed.
22 | AudioMaximumFrameSize = AudioSampleRate / 1000 * 60
23 |
24 | // AudioDefaultDataBytes is the default number of bytes that an audio frame
25 | // can use.
26 | AudioDefaultDataBytes = 40
27 |
28 | // AudioChannels is the number of audio channels that are contained in an
29 | // audio stream.
30 | AudioChannels = 1
31 | )
32 |
33 | // AudioListener is the interface that must be implemented by types wishing to
34 | // receive incoming audio data from the server.
35 | //
36 | // OnAudioStream is called when an audio stream for a user starts. It is the
37 | // implementer's responsibility to continuously process AudioStreamEvent.C
38 | // until it is closed.
39 | type AudioListener interface {
40 | OnAudioStream(e *AudioStreamEvent)
41 | }
42 |
43 | // AudioStreamEvent is event that is passed to AudioListener.OnAudioStream.
44 | type AudioStreamEvent struct {
45 | Client *Client
46 | User *User
47 | C <-chan *AudioPacket
48 | }
49 |
50 | // AudioBuffer is a slice of PCM audio samples.
51 | type AudioBuffer []int16
52 |
53 | func (a AudioBuffer) writeAudio(client *Client, seq int64, final bool) error {
54 | encoder := client.AudioEncoder
55 | if encoder == nil {
56 | return nil
57 | }
58 | dataBytes := client.Config.AudioDataBytes
59 | raw, err := encoder.Encode(a, len(a), dataBytes)
60 | if final {
61 | defer encoder.Reset()
62 | }
63 | if err != nil {
64 | return err
65 | }
66 |
67 | var targetID byte
68 | if target := client.VoiceTarget; target != nil {
69 | targetID = byte(target.ID)
70 | }
71 | // TODO: re-enable positional audio
72 | return client.Conn.WriteAudio(byte(4), targetID, seq, final, raw, nil, nil, nil)
73 | }
74 |
75 | // AudioPacket contains incoming audio samples and information.
76 | type AudioPacket struct {
77 | Client *Client
78 | Sender *User
79 | Target *VoiceTarget
80 |
81 | AudioBuffer
82 |
83 | HasPosition bool
84 | X, Y, Z float32
85 | }
86 |
--------------------------------------------------------------------------------
/gumble/bans.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "net"
5 | "time"
6 |
7 | "github.com/golang/protobuf/proto"
8 | "layeh.com/gumble/gumble/MumbleProto"
9 | )
10 |
11 | // BanList is a list of server ban entries.
12 | //
13 | // Whenever a ban is changed, it does not come into effect until the ban list
14 | // is sent back to the server.
15 | type BanList []*Ban
16 |
17 | // Add creates a new ban list entry with the given parameters.
18 | func (b *BanList) Add(address net.IP, mask net.IPMask, reason string, duration time.Duration) *Ban {
19 | ban := &Ban{
20 | Address: address,
21 | Mask: mask,
22 | Reason: reason,
23 | Duration: duration,
24 | }
25 | *b = append(*b, ban)
26 | return ban
27 | }
28 |
29 | // Ban represents an entry in the server ban list.
30 | //
31 | // This type should not be initialized manually. Instead, create new ban
32 | // entries using BanList.Add().
33 | type Ban struct {
34 | // The banned IP address.
35 | Address net.IP
36 | // The IP mask that the ban applies to.
37 | Mask net.IPMask
38 | // The name of the banned user.
39 | Name string
40 | // The certificate hash of the banned user.
41 | Hash string
42 | // The reason for the ban.
43 | Reason string
44 | // The start time from which the ban applies.
45 | Start time.Time
46 | // How long the ban is for.
47 | Duration time.Duration
48 |
49 | unban bool
50 | }
51 |
52 | // SetAddress sets the banned IP address.
53 | func (b *Ban) SetAddress(address net.IP) {
54 | b.Address = address
55 | }
56 |
57 | // SetMask sets the IP mask that the ban applies to.
58 | func (b *Ban) SetMask(mask net.IPMask) {
59 | b.Mask = mask
60 | }
61 |
62 | // SetReason changes the reason for the ban.
63 | func (b *Ban) SetReason(reason string) {
64 | b.Reason = reason
65 | }
66 |
67 | // SetDuration changes the duration of the ban.
68 | func (b *Ban) SetDuration(duration time.Duration) {
69 | b.Duration = duration
70 | }
71 |
72 | // Unban will unban the user from the server.
73 | func (b *Ban) Unban() {
74 | b.unban = true
75 | }
76 |
77 | // Ban will ban the user from the server. This is only useful if Unban() was
78 | // called on the ban entry.
79 | func (b *Ban) Ban() {
80 | b.unban = false
81 | }
82 |
83 | func (b BanList) writeMessage(client *Client) error {
84 | packet := MumbleProto.BanList{
85 | Query: proto.Bool(false),
86 | }
87 |
88 | for _, ban := range b {
89 | if !ban.unban {
90 | maskSize, _ := ban.Mask.Size()
91 | packet.Bans = append(packet.Bans, &MumbleProto.BanList_BanEntry{
92 | Address: ban.Address,
93 | Mask: proto.Uint32(uint32(maskSize)),
94 | Reason: &ban.Reason,
95 | Duration: proto.Uint32(uint32(ban.Duration / time.Second)),
96 | })
97 | }
98 | }
99 |
100 | return client.Conn.WriteProto(&packet)
101 | }
102 |
--------------------------------------------------------------------------------
/gumble/ping.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/binary"
6 | "errors"
7 | "io"
8 | "net"
9 | "sync"
10 | "time"
11 | )
12 |
13 | // PingResponse contains information about a server that responded to a UDP
14 | // ping packet.
15 | type PingResponse struct {
16 | // The address of the pinged server.
17 | Address *net.UDPAddr
18 | // The round-trip time from the client to the server.
19 | Ping time.Duration
20 | // The server's version. Only the Version field and SemanticVersion method of
21 | // the value will be valid.
22 | Version Version
23 | // The number users currently connected to the server.
24 | ConnectedUsers int
25 | // The maximum number of users that can connect to the server.
26 | MaximumUsers int
27 | // The maximum audio bitrate per user for the server.
28 | MaximumBitrate int
29 | }
30 |
31 | // Ping sends a UDP ping packet to the given server. If interval is positive,
32 | // the packet is retransmitted at every interval.
33 | //
34 | // Returns a PingResponse and nil on success. The function will return nil and
35 | // an error if a valid response is not received after the given timeout.
36 | func Ping(address string, interval, timeout time.Duration) (*PingResponse, error) {
37 | if timeout < 0 {
38 | return nil, errors.New("gumble: timeout must be positive")
39 | }
40 | deadline := time.Now().Add(timeout)
41 | conn, err := net.DialTimeout("udp", address, timeout)
42 | if err != nil {
43 | return nil, err
44 | }
45 | defer conn.Close()
46 | conn.SetReadDeadline(deadline)
47 |
48 | var (
49 | idsLock sync.Mutex
50 | ids = make(map[string]time.Time)
51 | )
52 |
53 | buildSendPacket := func() {
54 | var packet [12]byte
55 | if _, err := rand.Read(packet[4:]); err != nil {
56 | return
57 | }
58 | id := string(packet[4:])
59 | idsLock.Lock()
60 | ids[id] = time.Now()
61 | idsLock.Unlock()
62 | conn.Write(packet[:])
63 | }
64 |
65 | if interval > 0 {
66 | end := make(chan struct{})
67 | defer close(end)
68 | go func() {
69 | ticker := time.NewTicker(interval)
70 | defer ticker.Stop()
71 | for {
72 | select {
73 | case <-ticker.C:
74 | buildSendPacket()
75 | case <-end:
76 | return
77 | }
78 | }
79 | }()
80 | }
81 |
82 | buildSendPacket()
83 |
84 | for {
85 | var incoming [24]byte
86 | if _, err := io.ReadFull(conn, incoming[:]); err != nil {
87 | return nil, err
88 | }
89 | id := string(incoming[4:12])
90 | idsLock.Lock()
91 | sendTime, ok := ids[id]
92 | idsLock.Unlock()
93 | if !ok {
94 | continue
95 | }
96 |
97 | return &PingResponse{
98 | Address: conn.RemoteAddr().(*net.UDPAddr),
99 | Ping: time.Since(sendTime),
100 | Version: Version{
101 | Version: binary.BigEndian.Uint32(incoming[0:]),
102 | },
103 | ConnectedUsers: int(binary.BigEndian.Uint32(incoming[12:])),
104 | MaximumUsers: int(binary.BigEndian.Uint32(incoming[16:])),
105 | MaximumBitrate: int(binary.BigEndian.Uint32(incoming[20:])),
106 | }, nil
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/gumbleutil/listener.go:
--------------------------------------------------------------------------------
1 | package gumbleutil
2 |
3 | import (
4 | "layeh.com/gumble/gumble"
5 | )
6 |
7 | // Listener is a struct that implements the gumble.EventListener interface. The
8 | // corresponding event function in the struct is called if it is non-nil.
9 | type Listener struct {
10 | Connect func(e *gumble.ConnectEvent)
11 | Disconnect func(e *gumble.DisconnectEvent)
12 | TextMessage func(e *gumble.TextMessageEvent)
13 | UserChange func(e *gumble.UserChangeEvent)
14 | ChannelChange func(e *gumble.ChannelChangeEvent)
15 | PermissionDenied func(e *gumble.PermissionDeniedEvent)
16 | UserList func(e *gumble.UserListEvent)
17 | ACL func(e *gumble.ACLEvent)
18 | BanList func(e *gumble.BanListEvent)
19 | ContextActionChange func(e *gumble.ContextActionChangeEvent)
20 | ServerConfig func(e *gumble.ServerConfigEvent)
21 | }
22 |
23 | var _ gumble.EventListener = (*Listener)(nil)
24 |
25 | // OnConnect implements gumble.EventListener.OnConnect.
26 | func (l Listener) OnConnect(e *gumble.ConnectEvent) {
27 | if l.Connect != nil {
28 | l.Connect(e)
29 | }
30 | }
31 |
32 | // OnDisconnect implements gumble.EventListener.OnDisconnect.
33 | func (l Listener) OnDisconnect(e *gumble.DisconnectEvent) {
34 | if l.Disconnect != nil {
35 | l.Disconnect(e)
36 | }
37 | }
38 |
39 | // OnTextMessage implements gumble.EventListener.OnTextMessage.
40 | func (l Listener) OnTextMessage(e *gumble.TextMessageEvent) {
41 | if l.TextMessage != nil {
42 | l.TextMessage(e)
43 | }
44 | }
45 |
46 | // OnUserChange implements gumble.EventListener.OnUserChange.
47 | func (l Listener) OnUserChange(e *gumble.UserChangeEvent) {
48 | if l.UserChange != nil {
49 | l.UserChange(e)
50 | }
51 | }
52 |
53 | // OnChannelChange implements gumble.EventListener.OnChannelChange.
54 | func (l Listener) OnChannelChange(e *gumble.ChannelChangeEvent) {
55 | if l.ChannelChange != nil {
56 | l.ChannelChange(e)
57 | }
58 | }
59 |
60 | // OnPermissionDenied implements gumble.EventListener.OnPermissionDenied.
61 | func (l Listener) OnPermissionDenied(e *gumble.PermissionDeniedEvent) {
62 | if l.PermissionDenied != nil {
63 | l.PermissionDenied(e)
64 | }
65 | }
66 |
67 | // OnUserList implements gumble.EventListener.OnUserList.
68 | func (l Listener) OnUserList(e *gumble.UserListEvent) {
69 | if l.UserList != nil {
70 | l.UserList(e)
71 | }
72 | }
73 |
74 | // OnACL implements gumble.EventListener.OnACL.
75 | func (l Listener) OnACL(e *gumble.ACLEvent) {
76 | if l.ACL != nil {
77 | l.ACL(e)
78 | }
79 | }
80 |
81 | // OnBanList implements gumble.EventListener.OnBanList.
82 | func (l Listener) OnBanList(e *gumble.BanListEvent) {
83 | if l.BanList != nil {
84 | l.BanList(e)
85 | }
86 | }
87 |
88 | // OnContextActionChange implements gumble.EventListener.OnContextActionChange.
89 | func (l Listener) OnContextActionChange(e *gumble.ContextActionChangeEvent) {
90 | if l.ContextActionChange != nil {
91 | l.ContextActionChange(e)
92 | }
93 | }
94 |
95 | // OnServerConfig implements gumble.EventListener.OnServerConfig.
96 | func (l Listener) OnServerConfig(e *gumble.ServerConfigEvent) {
97 | if l.ServerConfig != nil {
98 | l.ServerConfig(e)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/gumble/acl.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "github.com/golang/protobuf/proto"
5 | "layeh.com/gumble/gumble/MumbleProto"
6 | )
7 |
8 | // ACL contains a list of ACLGroups and ACLRules linked to a channel.
9 | type ACL struct {
10 | // The channel to which the ACL belongs.
11 | Channel *Channel
12 | // The ACL's groups.
13 | Groups []*ACLGroup
14 | // The ACL's rules.
15 | Rules []*ACLRule
16 | // Does the ACL inherits the parent channel's ACLs?
17 | Inherits bool
18 | }
19 |
20 | func (a *ACL) writeMessage(client *Client) error {
21 | packet := MumbleProto.ACL{
22 | ChannelId: &a.Channel.ID,
23 | Groups: make([]*MumbleProto.ACL_ChanGroup, len(a.Groups)),
24 | Acls: make([]*MumbleProto.ACL_ChanACL, len(a.Rules)),
25 | InheritAcls: &a.Inherits,
26 | Query: proto.Bool(false),
27 | }
28 |
29 | for i, group := range a.Groups {
30 | packet.Groups[i] = &MumbleProto.ACL_ChanGroup{
31 | Name: &group.Name,
32 | Inherit: &group.InheritUsers,
33 | Inheritable: &group.Inheritable,
34 | Add: make([]uint32, 0, len(group.UsersAdd)),
35 | Remove: make([]uint32, 0, len(group.UsersRemove)),
36 | }
37 | for _, user := range group.UsersAdd {
38 | packet.Groups[i].Add = append(packet.Groups[i].Add, user.UserID)
39 | }
40 | for _, user := range group.UsersRemove {
41 | packet.Groups[i].Remove = append(packet.Groups[i].Remove, user.UserID)
42 | }
43 | }
44 |
45 | for i, rule := range a.Rules {
46 | packet.Acls[i] = &MumbleProto.ACL_ChanACL{
47 | ApplyHere: &rule.AppliesCurrent,
48 | ApplySubs: &rule.AppliesChildren,
49 | Grant: proto.Uint32(uint32(rule.Granted)),
50 | Deny: proto.Uint32(uint32(rule.Denied)),
51 | }
52 | if rule.User != nil {
53 | packet.Acls[i].UserId = &rule.User.UserID
54 | }
55 | if rule.Group != nil {
56 | packet.Acls[i].Group = &rule.Group.Name
57 | }
58 | }
59 |
60 | return client.Conn.WriteProto(&packet)
61 | }
62 |
63 | // ACLUser is a registered user who is part of or can be part of an ACL group
64 | // or rule.
65 | type ACLUser struct {
66 | // The user ID of the user.
67 | UserID uint32
68 | // The name of the user.
69 | Name string
70 | }
71 |
72 | // ACLGroup is a named group of registered users which can be used in an
73 | // ACLRule.
74 | type ACLGroup struct {
75 | // The ACL group name.
76 | Name string
77 | // Is the group inherited from the parent channel's ACL?
78 | Inherited bool
79 | // Are group members are inherited from the parent channel's ACL?
80 | InheritUsers bool
81 | // Can the group be inherited by child channels?
82 | Inheritable bool
83 | // The users who are explicitly added to, explicitly removed from, and
84 | // inherited into the group.
85 | UsersAdd, UsersRemove, UsersInherited map[uint32]*ACLUser
86 | }
87 |
88 | // ACL group names that are built-in.
89 | const (
90 | ACLGroupEveryone = "all"
91 | ACLGroupAuthenticated = "auth"
92 | ACLGroupInsideChannel = "in"
93 | ACLGroupOutsideChannel = "out"
94 | )
95 |
96 | // ACLRule is a set of granted and denied permissions given to an ACLUser or
97 | // ACLGroup.
98 | type ACLRule struct {
99 | // Does the rule apply to the channel in which the rule is defined?
100 | AppliesCurrent bool
101 | // Does the rule apply to the children of the channel in which the rule is
102 | // defined?
103 | AppliesChildren bool
104 | // Is the rule inherited from the parent channel's ACL?
105 | Inherited bool
106 |
107 | // The permissions granted by the rule.
108 | Granted Permission
109 | // The permissions denied by the rule.
110 | Denied Permission
111 |
112 | // The ACL user the rule applies to. Can be nil.
113 | User *ACLUser
114 | // The ACL group the rule applies to. Can be nil.
115 | Group *ACLGroup
116 | }
117 |
--------------------------------------------------------------------------------
/gumbleopenal/stream.go:
--------------------------------------------------------------------------------
1 | package gumbleopenal
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "time"
7 |
8 | "github.com/dchote/go-openal/openal"
9 | "layeh.com/gumble/gumble"
10 | )
11 |
12 | var (
13 | ErrState = errors.New("gumbleopenal: invalid state")
14 | )
15 |
16 | type Stream struct {
17 | client *gumble.Client
18 | link gumble.Detacher
19 |
20 | deviceSource *openal.CaptureDevice
21 | sourceFrameSize int
22 | sourceStop chan bool
23 |
24 | deviceSink *openal.Device
25 | contextSink *openal.Context
26 | }
27 |
28 | func New(client *gumble.Client) (*Stream, error) {
29 | s := &Stream{
30 | client: client,
31 | sourceFrameSize: client.Config.AudioFrameSize(),
32 | }
33 |
34 | s.deviceSource = openal.CaptureOpenDevice("", gumble.AudioSampleRate, openal.FormatMono16, uint32(s.sourceFrameSize))
35 |
36 | s.deviceSink = openal.OpenDevice("")
37 | s.contextSink = s.deviceSink.CreateContext()
38 | s.contextSink.Activate()
39 |
40 | s.link = client.Config.AttachAudio(s)
41 |
42 | return s, nil
43 | }
44 |
45 | func (s *Stream) Destroy() {
46 | s.link.Detach()
47 | if s.deviceSource != nil {
48 | s.StopSource()
49 | s.deviceSource.CaptureCloseDevice()
50 | s.deviceSource = nil
51 | }
52 | if s.deviceSink != nil {
53 | s.contextSink.Destroy()
54 | s.deviceSink.CloseDevice()
55 | s.contextSink = nil
56 | s.deviceSink = nil
57 | }
58 | }
59 |
60 | func (s *Stream) StartSource() error {
61 | if s.sourceStop != nil {
62 | return ErrState
63 | }
64 | s.deviceSource.CaptureStart()
65 | s.sourceStop = make(chan bool)
66 | go s.sourceRoutine()
67 | return nil
68 | }
69 |
70 | func (s *Stream) StopSource() error {
71 | if s.sourceStop == nil {
72 | return ErrState
73 | }
74 | close(s.sourceStop)
75 | s.sourceStop = nil
76 | s.deviceSource.CaptureStop()
77 | return nil
78 | }
79 |
80 | func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
81 | go func() {
82 | source := openal.NewSource()
83 | emptyBufs := openal.NewBuffers(8)
84 | reclaim := func() {
85 | if n := source.BuffersProcessed(); n > 0 {
86 | reclaimedBufs := make(openal.Buffers, n)
87 | source.UnqueueBuffers(reclaimedBufs)
88 | emptyBufs = append(emptyBufs, reclaimedBufs...)
89 | }
90 | }
91 | var raw [gumble.AudioMaximumFrameSize * 2]byte
92 | for packet := range e.C {
93 | samples := len(packet.AudioBuffer)
94 | if samples > cap(raw) {
95 | continue
96 | }
97 | for i, value := range packet.AudioBuffer {
98 | binary.LittleEndian.PutUint16(raw[i*2:], uint16(value))
99 | }
100 | reclaim()
101 | if len(emptyBufs) == 0 {
102 | continue
103 | }
104 | last := len(emptyBufs) - 1
105 | buffer := emptyBufs[last]
106 | emptyBufs = emptyBufs[:last]
107 | buffer.SetData(openal.FormatMono16, raw[:samples*2], gumble.AudioSampleRate)
108 | source.QueueBuffer(buffer)
109 | if source.State() != openal.Playing {
110 | source.Play()
111 | }
112 | }
113 | reclaim()
114 | emptyBufs.Delete()
115 | source.Delete()
116 | }()
117 | }
118 |
119 | func (s *Stream) sourceRoutine() {
120 | interval := s.client.Config.AudioInterval
121 | frameSize := s.client.Config.AudioFrameSize()
122 |
123 | if frameSize != s.sourceFrameSize {
124 | s.deviceSource.CaptureCloseDevice()
125 | s.sourceFrameSize = frameSize
126 | s.deviceSource = openal.CaptureOpenDevice("", gumble.AudioSampleRate, openal.FormatMono16, uint32(s.sourceFrameSize))
127 | }
128 |
129 | ticker := time.NewTicker(interval)
130 | defer ticker.Stop()
131 |
132 | stop := s.sourceStop
133 |
134 | outgoing := s.client.AudioOutgoing()
135 | defer close(outgoing)
136 |
137 | for {
138 | select {
139 | case <-stop:
140 | return
141 | case <-ticker.C:
142 | buff := s.deviceSource.CaptureSamples(uint32(frameSize))
143 | if len(buff) != frameSize*2 {
144 | continue
145 | }
146 | int16Buffer := make([]int16, frameSize)
147 | for i := range int16Buffer {
148 | int16Buffer[i] = int16(binary.LittleEndian.Uint16(buff[i*2 : (i+1)*2]))
149 | }
150 | outgoing <- gumble.AudioBuffer(int16Buffer)
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/gumble/listeners.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | type eventItem struct {
4 | parent *Listeners
5 | prev, next *eventItem
6 | listener EventListener
7 | }
8 |
9 | func (e *eventItem) Detach() {
10 | if e.prev == nil {
11 | e.parent.head = e.next
12 | } else {
13 | e.prev.next = e.next
14 | }
15 | if e.next == nil {
16 | e.parent.tail = e.prev
17 | } else {
18 | e.next.prev = e.prev
19 | }
20 | }
21 |
22 | // Listeners is a list of event listeners. Each attached listener is called in
23 | // sequence when a Client event is triggered.
24 | type Listeners struct {
25 | head, tail *eventItem
26 | }
27 |
28 | // Attach adds a new event listener to the end of the current list of listeners.
29 | func (e *Listeners) Attach(listener EventListener) Detacher {
30 | item := &eventItem{
31 | parent: e,
32 | prev: e.tail,
33 | listener: listener,
34 | }
35 | if e.head == nil {
36 | e.head = item
37 | }
38 | if e.tail != nil {
39 | e.tail.next = item
40 | }
41 | e.tail = item
42 | return item
43 | }
44 |
45 | func (e *Listeners) onConnect(event *ConnectEvent) {
46 | event.Client.volatile.Lock()
47 | for item := e.head; item != nil; item = item.next {
48 | event.Client.volatile.Unlock()
49 | item.listener.OnConnect(event)
50 | event.Client.volatile.Lock()
51 | }
52 | event.Client.volatile.Unlock()
53 | }
54 |
55 | func (e *Listeners) onDisconnect(event *DisconnectEvent) {
56 | event.Client.volatile.Lock()
57 | for item := e.head; item != nil; item = item.next {
58 | event.Client.volatile.Unlock()
59 | item.listener.OnDisconnect(event)
60 | event.Client.volatile.Lock()
61 | }
62 | event.Client.volatile.Unlock()
63 | }
64 |
65 | func (e *Listeners) onTextMessage(event *TextMessageEvent) {
66 | event.Client.volatile.Lock()
67 | for item := e.head; item != nil; item = item.next {
68 | event.Client.volatile.Unlock()
69 | item.listener.OnTextMessage(event)
70 | event.Client.volatile.Lock()
71 | }
72 | event.Client.volatile.Unlock()
73 | }
74 |
75 | func (e *Listeners) onUserChange(event *UserChangeEvent) {
76 | event.Client.volatile.Lock()
77 | for item := e.head; item != nil; item = item.next {
78 | event.Client.volatile.Unlock()
79 | item.listener.OnUserChange(event)
80 | event.Client.volatile.Lock()
81 | }
82 | event.Client.volatile.Unlock()
83 | }
84 |
85 | func (e *Listeners) onChannelChange(event *ChannelChangeEvent) {
86 | event.Client.volatile.Lock()
87 | for item := e.head; item != nil; item = item.next {
88 | event.Client.volatile.Unlock()
89 | item.listener.OnChannelChange(event)
90 | event.Client.volatile.Lock()
91 | }
92 | event.Client.volatile.Unlock()
93 | }
94 |
95 | func (e *Listeners) onPermissionDenied(event *PermissionDeniedEvent) {
96 | event.Client.volatile.Lock()
97 | for item := e.head; item != nil; item = item.next {
98 | event.Client.volatile.Unlock()
99 | item.listener.OnPermissionDenied(event)
100 | event.Client.volatile.Lock()
101 | }
102 | event.Client.volatile.Unlock()
103 | }
104 |
105 | func (e *Listeners) onUserList(event *UserListEvent) {
106 | event.Client.volatile.Lock()
107 | for item := e.head; item != nil; item = item.next {
108 | event.Client.volatile.Unlock()
109 | item.listener.OnUserList(event)
110 | event.Client.volatile.Lock()
111 | }
112 | event.Client.volatile.Unlock()
113 | }
114 |
115 | func (e *Listeners) onACL(event *ACLEvent) {
116 | event.Client.volatile.Lock()
117 | for item := e.head; item != nil; item = item.next {
118 | event.Client.volatile.Unlock()
119 | item.listener.OnACL(event)
120 | event.Client.volatile.Lock()
121 | }
122 | event.Client.volatile.Unlock()
123 | }
124 |
125 | func (e *Listeners) onBanList(event *BanListEvent) {
126 | event.Client.volatile.Lock()
127 | for item := e.head; item != nil; item = item.next {
128 | event.Client.volatile.Unlock()
129 | item.listener.OnBanList(event)
130 | event.Client.volatile.Lock()
131 | }
132 | event.Client.volatile.Unlock()
133 | }
134 |
135 | func (e *Listeners) onContextActionChange(event *ContextActionChangeEvent) {
136 | event.Client.volatile.Lock()
137 | for item := e.head; item != nil; item = item.next {
138 | event.Client.volatile.Unlock()
139 | item.listener.OnContextActionChange(event)
140 | event.Client.volatile.Lock()
141 | }
142 | event.Client.volatile.Unlock()
143 | }
144 |
145 | func (e *Listeners) onServerConfig(event *ServerConfigEvent) {
146 | event.Client.volatile.Lock()
147 | for item := e.head; item != nil; item = item.next {
148 | event.Client.volatile.Unlock()
149 | item.listener.OnServerConfig(event)
150 | event.Client.volatile.Lock()
151 | }
152 | event.Client.volatile.Unlock()
153 | }
154 |
--------------------------------------------------------------------------------
/gumbleffmpeg/stream.go:
--------------------------------------------------------------------------------
1 | package gumbleffmpeg
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "io"
7 | "os/exec"
8 | "strconv"
9 | "sync"
10 | "sync/atomic"
11 | "time"
12 |
13 | "layeh.com/gumble/gumble"
14 | )
15 |
16 | // State represents the state of a Stream.
17 | type State int32
18 |
19 | // Valid states of Stream.
20 | const (
21 | StateInitial State = iota + 1
22 | StatePlaying
23 | StatePaused
24 | StateStopped
25 | )
26 |
27 | // Stream is an audio stream that encodes media through ffmpeg and sends it to
28 | // the server.
29 | //
30 | // A stream can only be used once; it cannot be started after it is stopped.
31 | type Stream struct {
32 | // Command to execute to play the file. Defaults to "ffmpeg".
33 | Command string
34 | // Playback volume (can be changed while the source is playing).
35 | Volume float32
36 | // Audio source (cannot be changed after stream starts).
37 | Source Source
38 | // Starting offset.
39 | Offset time.Duration
40 |
41 | client *gumble.Client
42 | cmd *exec.Cmd
43 | pipe io.ReadCloser
44 | pause chan struct{}
45 | elapsed int64
46 |
47 | state State
48 |
49 | l sync.Mutex
50 | wg sync.WaitGroup
51 | }
52 |
53 | // New returns a new Stream for the given gumble Client and Source.
54 | func New(client *gumble.Client, source Source) *Stream {
55 | return &Stream{
56 | client: client,
57 | Volume: 1.0,
58 | Source: source,
59 | Command: "ffmpeg",
60 | pause: make(chan struct{}),
61 | state: StateInitial,
62 | }
63 | }
64 |
65 | // Play begins playing
66 | func (s *Stream) Play() error {
67 | s.l.Lock()
68 | defer s.l.Unlock()
69 |
70 | switch s.state {
71 | case StatePaused:
72 | s.state = StatePlaying
73 | go s.process()
74 | return nil
75 | case StatePlaying:
76 | return errors.New("gumbleffmpeg: stream already playing")
77 | case StateStopped:
78 | return errors.New("gumbleffmpeg: stream has stopped")
79 | }
80 |
81 | // fresh stream
82 | if s.Source == nil {
83 | return errors.New("gumbleffmpeg: nil source")
84 | }
85 |
86 | args := s.Source.arguments()
87 | if s.Offset > 0 {
88 | args = append([]string{"-ss", strconv.FormatFloat(s.Offset.Seconds(), 'f', -1, 64)}, args...)
89 | }
90 | args = append(args, "-ac", strconv.Itoa(gumble.AudioChannels), "-ar", strconv.Itoa(gumble.AudioSampleRate), "-f", "s16le", "-")
91 | cmd := exec.Command(s.Command, args...)
92 | var err error
93 | s.pipe, err = cmd.StdoutPipe()
94 | if err != nil {
95 | return err
96 | }
97 | if err := s.Source.start(cmd); err != nil {
98 | return err
99 | }
100 | if err := cmd.Start(); err != nil {
101 | s.Source.done()
102 | return err
103 | }
104 | s.wg.Add(1)
105 | s.cmd = cmd
106 | s.state = StatePlaying
107 | go s.process()
108 | return nil
109 | }
110 |
111 | // State returns the state of the stream.
112 | func (s *Stream) State() State {
113 | s.l.Lock()
114 | defer s.l.Unlock()
115 | return s.state
116 | }
117 |
118 | // Pause pauses a playing stream.
119 | func (s *Stream) Pause() error {
120 | s.l.Lock()
121 | if s.state != StatePlaying {
122 | s.l.Unlock()
123 | return errors.New("gumbleffmpeg: stream is not playing")
124 | }
125 | s.state = StatePaused
126 | s.l.Unlock()
127 | s.pause <- struct{}{}
128 | return nil
129 | }
130 |
131 | // Stop stops the stream.
132 | func (s *Stream) Stop() error {
133 | s.l.Lock()
134 | switch s.state {
135 | case StateStopped, StateInitial:
136 | s.l.Unlock()
137 | return errors.New("gumbleffmpeg: stream is not playing nor paused")
138 | }
139 | s.cleanup()
140 | s.Wait()
141 | return nil
142 | }
143 |
144 | // Wait returns once the stream has stopped playing.
145 | func (s *Stream) Wait() {
146 | s.wg.Wait()
147 | }
148 |
149 | // Elapsed returns the amount of audio that has been played by the stream.
150 | func (s *Stream) Elapsed() time.Duration {
151 | return time.Duration(atomic.LoadInt64(&s.elapsed))
152 | }
153 |
154 | func (s *Stream) process() {
155 | // s.state has been set to StatePlaying
156 |
157 | interval := s.client.Config.AudioInterval
158 | frameSize := s.client.Config.AudioFrameSize()
159 |
160 | byteBuffer := make([]byte, frameSize*2)
161 |
162 | outgoing := s.client.AudioOutgoing()
163 | defer close(outgoing)
164 |
165 | ticker := time.NewTicker(interval)
166 | defer ticker.Stop()
167 |
168 | for {
169 | select {
170 | case <-s.pause:
171 | return
172 | case <-ticker.C:
173 | if _, err := io.ReadFull(s.pipe, byteBuffer); err != nil {
174 | s.l.Lock()
175 | s.cleanup()
176 | return
177 | }
178 | int16Buffer := make([]int16, frameSize)
179 | for i := range int16Buffer {
180 | float := float32(int16(binary.LittleEndian.Uint16(byteBuffer[i*2 : (i+1)*2])))
181 | int16Buffer[i] = int16(s.Volume * float)
182 | }
183 | atomic.AddInt64(&s.elapsed, int64(interval))
184 | outgoing <- gumble.AudioBuffer(int16Buffer)
185 | }
186 | }
187 | }
188 |
189 | func (s *Stream) cleanup() {
190 | defer s.l.Unlock()
191 | // s.l has been acquired
192 | if s.state == StateStopped {
193 | return
194 | }
195 | s.cmd.Process.Kill()
196 | s.cmd.Wait()
197 | s.Source.done()
198 | for len(s.pause) > 0 {
199 | <-s.pause
200 | }
201 | s.state = StateStopped
202 | s.wg.Done()
203 | }
204 |
--------------------------------------------------------------------------------
/gumble/conn.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "io"
7 | "net"
8 | "sync"
9 | "time"
10 |
11 | "github.com/golang/protobuf/proto"
12 | "layeh.com/gumble/gumble/MumbleProto"
13 | "layeh.com/gumble/gumble/varint"
14 | )
15 |
16 | // DefaultPort is the default port on which Mumble servers listen.
17 | const DefaultPort = 64738
18 |
19 | // Conn represents a control protocol connection to a Mumble client/server.
20 | type Conn struct {
21 | sync.Mutex
22 | net.Conn
23 |
24 | MaximumPacketBytes int
25 | Timeout time.Duration
26 |
27 | buffer []byte
28 | }
29 |
30 | // NewConn creates a new Conn with the given net.Conn.
31 | func NewConn(conn net.Conn) *Conn {
32 | return &Conn{
33 | Conn: conn,
34 | Timeout: time.Second * 20,
35 | MaximumPacketBytes: 1024 * 1024 * 10,
36 | }
37 | }
38 |
39 | // ReadPacket reads a packet from the server. Returns the packet type, the
40 | // packet data, and nil on success.
41 | //
42 | // This function should only be called by a single go routine.
43 | func (c *Conn) ReadPacket() (uint16, []byte, error) {
44 | c.Conn.SetReadDeadline(time.Now().Add(c.Timeout))
45 | var header [6]byte
46 | if _, err := io.ReadFull(c.Conn, header[:]); err != nil {
47 | return 0, nil, err
48 | }
49 | pType := binary.BigEndian.Uint16(header[:])
50 | pLength := binary.BigEndian.Uint32(header[2:])
51 | pLengthInt := int(pLength)
52 | if pLengthInt > c.MaximumPacketBytes {
53 | return 0, nil, errors.New("gumble: packet larger than maximum allowed size")
54 | }
55 | if pLengthInt > len(c.buffer) {
56 | c.buffer = make([]byte, pLengthInt)
57 | }
58 | if _, err := io.ReadFull(c.Conn, c.buffer[:pLengthInt]); err != nil {
59 | return 0, nil, err
60 | }
61 | return pType, c.buffer[:pLengthInt], nil
62 | }
63 |
64 | // WriteAudio writes an audio packet to the connection.
65 | func (c *Conn) WriteAudio(format, target byte, sequence int64, final bool, data []byte, X, Y, Z *float32) error {
66 | var buff [1 + varint.MaxVarintLen*2]byte
67 | buff[0] = (format << 5) | target
68 | n := varint.Encode(buff[1:], sequence)
69 | if n == 0 {
70 | return errors.New("gumble: varint out of range")
71 | }
72 | l := int64(len(data))
73 | if final {
74 | l |= 0x2000
75 | }
76 | m := varint.Encode(buff[1+n:], l)
77 | if m == 0 {
78 | return errors.New("gumble: varint out of range")
79 | }
80 | header := buff[:1+n+m]
81 |
82 | var positionalLength int
83 | if X != nil {
84 | positionalLength = 3 * 4
85 | }
86 |
87 | c.Lock()
88 | defer c.Unlock()
89 |
90 | if err := c.writeHeader(1, uint32(len(header)+len(data)+positionalLength)); err != nil {
91 | return err
92 | }
93 | if _, err := c.Conn.Write(header); err != nil {
94 | return err
95 | }
96 | if _, err := c.Conn.Write(data); err != nil {
97 | return err
98 | }
99 |
100 | if positionalLength > 0 {
101 | if err := binary.Write(c.Conn, binary.LittleEndian, *X); err != nil {
102 | return err
103 | }
104 | if err := binary.Write(c.Conn, binary.LittleEndian, *Y); err != nil {
105 | return err
106 | }
107 | if err := binary.Write(c.Conn, binary.LittleEndian, *Z); err != nil {
108 | return err
109 | }
110 | }
111 |
112 | return nil
113 | }
114 |
115 | // WritePacket writes a data packet of the given type to the connection.
116 | func (c *Conn) WritePacket(ptype uint16, data []byte) error {
117 | c.Lock()
118 | defer c.Unlock()
119 | if err := c.writeHeader(uint16(ptype), uint32(len(data))); err != nil {
120 | return err
121 | }
122 | if _, err := c.Conn.Write(data); err != nil {
123 | return err
124 | }
125 | return nil
126 | }
127 |
128 | func (c *Conn) writeHeader(pType uint16, pLength uint32) error {
129 | var header [6]byte
130 | binary.BigEndian.PutUint16(header[:], pType)
131 | binary.BigEndian.PutUint32(header[2:], pLength)
132 | if _, err := c.Conn.Write(header[:]); err != nil {
133 | return err
134 | }
135 | return nil
136 | }
137 |
138 | // WriteProto writes a protocol buffer message to the connection.
139 | func (c *Conn) WriteProto(message proto.Message) error {
140 | var protoType uint16
141 | switch message.(type) {
142 | case *MumbleProto.Version:
143 | protoType = 0
144 | case *MumbleProto.Authenticate:
145 | protoType = 2
146 | case *MumbleProto.Ping:
147 | protoType = 3
148 | case *MumbleProto.Reject:
149 | protoType = 4
150 | case *MumbleProto.ServerSync:
151 | protoType = 5
152 | case *MumbleProto.ChannelRemove:
153 | protoType = 6
154 | case *MumbleProto.ChannelState:
155 | protoType = 7
156 | case *MumbleProto.UserRemove:
157 | protoType = 8
158 | case *MumbleProto.UserState:
159 | protoType = 9
160 | case *MumbleProto.BanList:
161 | protoType = 10
162 | case *MumbleProto.TextMessage:
163 | protoType = 11
164 | case *MumbleProto.PermissionDenied:
165 | protoType = 12
166 | case *MumbleProto.ACL:
167 | protoType = 13
168 | case *MumbleProto.QueryUsers:
169 | protoType = 14
170 | case *MumbleProto.CryptSetup:
171 | protoType = 15
172 | case *MumbleProto.ContextActionModify:
173 | protoType = 16
174 | case *MumbleProto.ContextAction:
175 | protoType = 17
176 | case *MumbleProto.UserList:
177 | protoType = 18
178 | case *MumbleProto.VoiceTarget:
179 | protoType = 19
180 | case *MumbleProto.PermissionQuery:
181 | protoType = 20
182 | case *MumbleProto.CodecVersion:
183 | protoType = 21
184 | case *MumbleProto.UserStats:
185 | protoType = 22
186 | case *MumbleProto.RequestBlob:
187 | protoType = 23
188 | case *MumbleProto.ServerConfig:
189 | protoType = 24
190 | case *MumbleProto.SuggestConfig:
191 | protoType = 25
192 | default:
193 | return errors.New("gumble: unknown message type")
194 | }
195 | data, err := proto.Marshal(message)
196 | if err != nil {
197 | return err
198 | }
199 | return c.WritePacket(protoType, data)
200 | }
201 |
--------------------------------------------------------------------------------
/gumble/channel.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "github.com/golang/protobuf/proto"
5 | "layeh.com/gumble/gumble/MumbleProto"
6 | )
7 |
8 | // Channel represents a channel in the server's channel tree.
9 | type Channel struct {
10 | // The channel's unique ID.
11 | ID uint32
12 | // The channel's name.
13 | Name string
14 | // The channel's parent. nil if the channel is the root channel.
15 | Parent *Channel
16 | // The channels directly underneath the channel.
17 | Children Channels
18 | // The channels that are linked to the channel.
19 | Links Channels
20 | // The users currently in the channel.
21 | Users Users
22 | // The channel's description. Contains the empty string if the channel does
23 | // not have a description, or if it needs to be requested.
24 | Description string
25 | // The channel's description hash. nil if Channel.Description has
26 | // been populated.
27 | DescriptionHash []byte
28 | // The maximum number of users allowed in the channel. If the value is zero,
29 | // the maximum number of users per-channel is dictated by the server's
30 | // "usersperchannel" setting.
31 | MaxUsers uint32
32 | // The position at which the channel should be displayed in an ordered list.
33 | Position int32
34 | // Is the channel temporary?
35 | Temporary bool
36 |
37 | client *Client
38 | }
39 |
40 | // IsRoot returns true if the channel is the server's root channel.
41 | func (c *Channel) IsRoot() bool {
42 | return c.ID == 0
43 | }
44 |
45 | // Add will add a sub-channel to the given channel.
46 | func (c *Channel) Add(name string, temporary bool) {
47 | packet := MumbleProto.ChannelState{
48 | Parent: &c.ID,
49 | Name: &name,
50 | Temporary: &temporary,
51 | }
52 | c.client.Conn.WriteProto(&packet)
53 | }
54 |
55 | // Remove will remove the given channel and all sub-channels from the server's
56 | // channel tree.
57 | func (c *Channel) Remove() {
58 | packet := MumbleProto.ChannelRemove{
59 | ChannelId: &c.ID,
60 | }
61 | c.client.Conn.WriteProto(&packet)
62 | }
63 |
64 | // SetName will set the name of the channel. This will have no effect if the
65 | // channel is the server's root channel.
66 | func (c *Channel) SetName(name string) {
67 | packet := MumbleProto.ChannelState{
68 | ChannelId: &c.ID,
69 | Name: &name,
70 | }
71 | c.client.Conn.WriteProto(&packet)
72 | }
73 |
74 | // SetDescription will set the description of the channel.
75 | func (c *Channel) SetDescription(description string) {
76 | packet := MumbleProto.ChannelState{
77 | ChannelId: &c.ID,
78 | Description: &description,
79 | }
80 | c.client.Conn.WriteProto(&packet)
81 | }
82 |
83 | // SetPosition will set the position of the channel.
84 | func (c *Channel) SetPosition(position int32) {
85 | packet := MumbleProto.ChannelState{
86 | ChannelId: &c.ID,
87 | Position: &position,
88 | }
89 | c.client.Conn.WriteProto(&packet)
90 | }
91 |
92 | // SetMaxUsers will set the maximum number of users allowed in the channel.
93 | func (c *Channel) SetMaxUsers(maxUsers uint32) {
94 | packet := MumbleProto.ChannelState{
95 | ChannelId: &c.ID,
96 | MaxUsers: &maxUsers,
97 | }
98 | c.client.Conn.WriteProto(&packet)
99 | }
100 |
101 | // Find returns a channel whose path (by channel name) from the current channel
102 | // is equal to the arguments passed.
103 | //
104 | // For example, given the following server channel tree:
105 | // Root
106 | // Child 1
107 | // Child 2
108 | // Child 2.1
109 | // Child 2.2
110 | // Child 2.2.1
111 | // Child 3
112 | // To get the "Child 2.2.1" channel:
113 | // root.Find("Child 2", "Child 2.2", "Child 2.2.1")
114 | func (c *Channel) Find(names ...string) *Channel {
115 | if len(names) == 0 {
116 | return c
117 | }
118 | for _, child := range c.Children {
119 | if child.Name == names[0] {
120 | return child.Find(names[1:]...)
121 | }
122 | }
123 | return nil
124 | }
125 |
126 | // RequestDescription requests that the actual channel description
127 | // (i.e. non-hashed) be sent to the client.
128 | func (c *Channel) RequestDescription() {
129 | packet := MumbleProto.RequestBlob{
130 | ChannelDescription: []uint32{c.ID},
131 | }
132 | c.client.Conn.WriteProto(&packet)
133 | }
134 |
135 | // RequestACL requests that the channel's ACL to be sent to the client.
136 | func (c *Channel) RequestACL() {
137 | packet := MumbleProto.ACL{
138 | ChannelId: &c.ID,
139 | Query: proto.Bool(true),
140 | }
141 | c.client.Conn.WriteProto(&packet)
142 | }
143 |
144 | // RequestPermission requests that the channel's permission information to be
145 | // sent to the client.
146 | //
147 | // Note: the server will not reply to the request if the client has up-to-date
148 | // permission information.
149 | func (c *Channel) RequestPermission() {
150 | packet := MumbleProto.PermissionQuery{
151 | ChannelId: &c.ID,
152 | }
153 | c.client.Conn.WriteProto(&packet)
154 | }
155 |
156 | // Send will send a text message to the channel.
157 | func (c *Channel) Send(message string, recursive bool) {
158 | textMessage := TextMessage{
159 | Message: message,
160 | }
161 | if recursive {
162 | textMessage.Trees = []*Channel{c}
163 | } else {
164 | textMessage.Channels = []*Channel{c}
165 | }
166 | c.client.Send(&textMessage)
167 | }
168 |
169 | // Permission returns the permissions the user has in the channel, or nil if
170 | // the permissions are unknown.
171 | func (c *Channel) Permission() *Permission {
172 | return c.client.permissions[c.ID]
173 | }
174 |
175 | // Link links the given channels to the channel.
176 | func (c *Channel) Link(channel ...*Channel) {
177 | packet := MumbleProto.ChannelState{
178 | ChannelId: &c.ID,
179 | LinksAdd: make([]uint32, len(channel)),
180 | }
181 | for i, ch := range channel {
182 | packet.LinksAdd[i] = ch.ID
183 | }
184 | c.client.Conn.WriteProto(&packet)
185 | }
186 |
187 | // Unlink unlinks the given channels from the channel. If no arguments are
188 | // passed, all linked channels are unlinked.
189 | func (c *Channel) Unlink(channel ...*Channel) {
190 | packet := MumbleProto.ChannelState{
191 | ChannelId: &c.ID,
192 | }
193 | if len(channel) == 0 {
194 | packet.LinksRemove = make([]uint32, len(c.Links))
195 | i := 0
196 | for channelID := range c.Links {
197 | packet.LinksRemove[i] = channelID
198 | i++
199 | }
200 | } else {
201 | packet.LinksRemove = make([]uint32, len(channel))
202 | for i, ch := range channel {
203 | packet.LinksRemove[i] = ch.ID
204 | }
205 | }
206 | c.client.Conn.WriteProto(&packet)
207 | }
208 |
--------------------------------------------------------------------------------
/gumble/user.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "github.com/golang/protobuf/proto"
5 | "layeh.com/gumble/gumble/MumbleProto"
6 | )
7 |
8 | // User represents a user that is currently connected to the server.
9 | type User struct {
10 | // The user's unique session ID.
11 | Session uint32
12 | // The user's ID. Contains an invalid value if the user is not registered.
13 | UserID uint32
14 | // The user's name.
15 | Name string
16 | // The channel that the user is currently in.
17 | Channel *Channel
18 |
19 | // Has the user has been muted?
20 | Muted bool
21 | // Has the user been deafened?
22 | Deafened bool
23 | // Has the user been suppressed?
24 | Suppressed bool
25 | // Has the user been muted by him/herself?
26 | SelfMuted bool
27 | // Has the user been deafened by him/herself?
28 | SelfDeafened bool
29 | // Is the user a priority speaker in the channel?
30 | PrioritySpeaker bool
31 | // Is the user recording audio?
32 | Recording bool
33 |
34 | // The user's comment. Contains the empty string if the user does not have a
35 | // comment, or if the comment needs to be requested.
36 | Comment string
37 | // The user's comment hash. nil if User.Comment has been populated.
38 | CommentHash []byte
39 | // The hash of the user's certificate (can be empty).
40 | Hash string
41 | // The user's texture (avatar). nil if the user does not have a
42 | // texture, or if the texture needs to be requested.
43 | Texture []byte
44 | // The user's texture hash. nil if User.Texture has been populated.
45 | TextureHash []byte
46 |
47 | // The user's stats. Contains nil if the stats have not yet been requested.
48 | Stats *UserStats
49 |
50 | client *Client
51 | decoder AudioDecoder
52 | }
53 |
54 | // SetTexture sets the user's texture.
55 | func (u *User) SetTexture(texture []byte) {
56 | packet := MumbleProto.UserState{
57 | Session: &u.Session,
58 | Texture: texture,
59 | }
60 | u.client.Conn.WriteProto(&packet)
61 | }
62 |
63 | // SetPrioritySpeaker sets if the user is a priority speaker in the channel.
64 | func (u *User) SetPrioritySpeaker(prioritySpeaker bool) {
65 | packet := MumbleProto.UserState{
66 | Session: &u.Session,
67 | PrioritySpeaker: &prioritySpeaker,
68 | }
69 | u.client.Conn.WriteProto(&packet)
70 | }
71 |
72 | // SetRecording sets if the user is recording audio.
73 | func (u *User) SetRecording(recording bool) {
74 | packet := MumbleProto.UserState{
75 | Session: &u.Session,
76 | Recording: &recording,
77 | }
78 | u.client.Conn.WriteProto(&packet)
79 | }
80 |
81 | // IsRegistered returns true if the user's certificate has been registered with
82 | // the server. A registered user will have a valid user ID.
83 | func (u *User) IsRegistered() bool {
84 | return u.UserID > 0
85 | }
86 |
87 | // Register will register the user with the server. If the client has
88 | // permission to do so, the user will shortly be given a UserID.
89 | func (u *User) Register() {
90 | packet := MumbleProto.UserState{
91 | Session: &u.Session,
92 | UserId: proto.Uint32(0),
93 | }
94 | u.client.Conn.WriteProto(&packet)
95 | }
96 |
97 | // SetComment will set the user's comment to the given string. The user's
98 | // comment will be erased if the comment is set to the empty string.
99 | func (u *User) SetComment(comment string) {
100 | packet := MumbleProto.UserState{
101 | Session: &u.Session,
102 | Comment: &comment,
103 | }
104 | u.client.Conn.WriteProto(&packet)
105 | }
106 |
107 | // Move will move the user to the given channel.
108 | func (u *User) Move(channel *Channel) {
109 | packet := MumbleProto.UserState{
110 | Session: &u.Session,
111 | ChannelId: &channel.ID,
112 | }
113 | u.client.Conn.WriteProto(&packet)
114 | }
115 |
116 | // Kick will kick the user from the server.
117 | func (u *User) Kick(reason string) {
118 | packet := MumbleProto.UserRemove{
119 | Session: &u.Session,
120 | Reason: &reason,
121 | }
122 | u.client.Conn.WriteProto(&packet)
123 | }
124 |
125 | // Ban will ban the user from the server.
126 | func (u *User) Ban(reason string) {
127 | packet := MumbleProto.UserRemove{
128 | Session: &u.Session,
129 | Reason: &reason,
130 | Ban: proto.Bool(true),
131 | }
132 | u.client.Conn.WriteProto(&packet)
133 | }
134 |
135 | // SetMuted sets whether the user can transmit audio or not.
136 | func (u *User) SetMuted(muted bool) {
137 | packet := MumbleProto.UserState{
138 | Session: &u.Session,
139 | Mute: &muted,
140 | }
141 | u.client.Conn.WriteProto(&packet)
142 | }
143 |
144 | // SetSuppressed sets whether the user is suppressed by the server or not.
145 | func (u *User) SetSuppressed(supressed bool) {
146 | packet := MumbleProto.UserState{
147 | Session: &u.Session,
148 | Suppress: &supressed,
149 | }
150 | u.client.Conn.WriteProto(&packet)
151 | }
152 |
153 | // SetDeafened sets whether the user can receive audio or not.
154 | func (u *User) SetDeafened(muted bool) {
155 | packet := MumbleProto.UserState{
156 | Session: &u.Session,
157 | Deaf: &muted,
158 | }
159 | u.client.Conn.WriteProto(&packet)
160 | }
161 |
162 | // SetSelfMuted sets whether the user can transmit audio or not.
163 | //
164 | // This method should only be called on Client.Self().
165 | func (u *User) SetSelfMuted(muted bool) {
166 | packet := MumbleProto.UserState{
167 | Session: &u.Session,
168 | SelfMute: &muted,
169 | }
170 | u.client.Conn.WriteProto(&packet)
171 | }
172 |
173 | // SetSelfDeafened sets whether the user can receive audio or not.
174 | //
175 | // This method should only be called on Client.Self().
176 | func (u *User) SetSelfDeafened(muted bool) {
177 | packet := MumbleProto.UserState{
178 | Session: &u.Session,
179 | SelfDeaf: &muted,
180 | }
181 | u.client.Conn.WriteProto(&packet)
182 | }
183 |
184 | // RequestStats requests that the user's stats be sent to the client.
185 | func (u *User) RequestStats() {
186 | packet := MumbleProto.UserStats{
187 | Session: &u.Session,
188 | }
189 | u.client.Conn.WriteProto(&packet)
190 | }
191 |
192 | // RequestTexture requests that the user's actual texture (i.e. non-hashed) be
193 | // sent to the client.
194 | func (u *User) RequestTexture() {
195 | packet := MumbleProto.RequestBlob{
196 | SessionTexture: []uint32{u.Session},
197 | }
198 | u.client.Conn.WriteProto(&packet)
199 | }
200 |
201 | // RequestComment requests that the user's actual comment (i.e. non-hashed) be
202 | // sent to the client.
203 | func (u *User) RequestComment() {
204 | packet := MumbleProto.RequestBlob{
205 | SessionComment: []uint32{u.Session},
206 | }
207 | u.client.Conn.WriteProto(&packet)
208 | }
209 |
210 | // Send will send a text message to the user.
211 | func (u *User) Send(message string) {
212 | textMessage := TextMessage{
213 | Users: []*User{u},
214 | Message: message,
215 | }
216 | u.client.Send(&textMessage)
217 | }
218 |
219 | // SetPlugin sets the user's plugin data.
220 | //
221 | // Plugins are currently only used for positional audio. Clients will receive
222 | // positional audio information from other users if their plugin context is the
223 | // same. The official Mumble client sets the context to:
224 | //
225 | // PluginShortName + "\x00" + AdditionalContextInformation
226 | func (u *User) SetPlugin(context []byte, identity string) {
227 | packet := MumbleProto.UserState{
228 | Session: &u.Session,
229 | PluginContext: context,
230 | PluginIdentity: &identity,
231 | }
232 | u.client.Conn.WriteProto(&packet)
233 | }
234 |
--------------------------------------------------------------------------------
/gumble/event.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "layeh.com/gumble/gumble/MumbleProto"
5 | )
6 |
7 | // EventListener is the interface that must be implemented by a type if it
8 | // wishes to be notified of Client events.
9 | //
10 | // Listener methods are executed synchronously as event happen. They also block
11 | // network reads from happening until all handlers for an event are called.
12 | // Therefore, it is not recommended to do any long processing from inside of
13 | // these methods.
14 | type EventListener interface {
15 | OnConnect(e *ConnectEvent)
16 | OnDisconnect(e *DisconnectEvent)
17 | OnTextMessage(e *TextMessageEvent)
18 | OnUserChange(e *UserChangeEvent)
19 | OnChannelChange(e *ChannelChangeEvent)
20 | OnPermissionDenied(e *PermissionDeniedEvent)
21 | OnUserList(e *UserListEvent)
22 | OnACL(e *ACLEvent)
23 | OnBanList(e *BanListEvent)
24 | OnContextActionChange(e *ContextActionChangeEvent)
25 | OnServerConfig(e *ServerConfigEvent)
26 | }
27 |
28 | // ConnectEvent is the event that is passed to EventListener.OnConnect.
29 | type ConnectEvent struct {
30 | Client *Client
31 | WelcomeMessage *string
32 | MaximumBitrate *int
33 | }
34 |
35 | // DisconnectType specifies why a Client disconnected from a server.
36 | type DisconnectType int
37 |
38 | // Client disconnect reasons.
39 | const (
40 | DisconnectError DisconnectType = iota + 1
41 | DisconnectKicked
42 | DisconnectBanned
43 | DisconnectUser
44 | )
45 |
46 | // Has returns true if the DisconnectType has changeType part of its bitmask.
47 | func (d DisconnectType) Has(changeType DisconnectType) bool {
48 | return d&changeType == changeType
49 | }
50 |
51 | // DisconnectEvent is the event that is passed to EventListener.OnDisconnect.
52 | type DisconnectEvent struct {
53 | Client *Client
54 | Type DisconnectType
55 |
56 | String string
57 | }
58 |
59 | // TextMessageEvent is the event that is passed to EventListener.OnTextMessage.
60 | type TextMessageEvent struct {
61 | Client *Client
62 | TextMessage
63 | }
64 |
65 | // UserChangeType is a bitmask of items that changed for a user.
66 | type UserChangeType int
67 |
68 | // User change items.
69 | const (
70 | UserChangeConnected UserChangeType = 1 << iota
71 | UserChangeDisconnected
72 | UserChangeKicked
73 | UserChangeBanned
74 | UserChangeRegistered
75 | UserChangeUnregistered
76 | UserChangeName
77 | UserChangeChannel
78 | UserChangeComment
79 | UserChangeAudio
80 | UserChangeTexture
81 | UserChangePrioritySpeaker
82 | UserChangeRecording
83 | UserChangeStats
84 | )
85 |
86 | // Has returns true if the UserChangeType has changeType part of its bitmask.
87 | func (u UserChangeType) Has(changeType UserChangeType) bool {
88 | return u&changeType == changeType
89 | }
90 |
91 | // UserChangeEvent is the event that is passed to EventListener.OnUserChange.
92 | type UserChangeEvent struct {
93 | Client *Client
94 | Type UserChangeType
95 | User *User
96 | Actor *User
97 |
98 | String string
99 | }
100 |
101 | // ChannelChangeType is a bitmask of items that changed for a channel.
102 | type ChannelChangeType int
103 |
104 | // Channel change items.
105 | const (
106 | ChannelChangeCreated ChannelChangeType = 1 << iota
107 | ChannelChangeRemoved
108 | ChannelChangeMoved
109 | ChannelChangeName
110 | ChannelChangeLinks
111 | ChannelChangeDescription
112 | ChannelChangePosition
113 | ChannelChangePermission
114 | ChannelChangeMaxUsers
115 | )
116 |
117 | // Has returns true if the ChannelChangeType has changeType part of its
118 | // bitmask.
119 | func (c ChannelChangeType) Has(changeType ChannelChangeType) bool {
120 | return c&changeType == changeType
121 | }
122 |
123 | // ChannelChangeEvent is the event that is passed to
124 | // EventListener.OnChannelChange.
125 | type ChannelChangeEvent struct {
126 | Client *Client
127 | Type ChannelChangeType
128 | Channel *Channel
129 | }
130 |
131 | // PermissionDeniedType specifies why a Client was denied permission to perform
132 | // a particular action.
133 | type PermissionDeniedType int
134 |
135 | // Permission denied types.
136 | const (
137 | PermissionDeniedOther PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_Text)
138 | PermissionDeniedPermission PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_Permission)
139 | PermissionDeniedSuperUser PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_SuperUser)
140 | PermissionDeniedInvalidChannelName PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_ChannelName)
141 | PermissionDeniedTextTooLong PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_TextTooLong)
142 | PermissionDeniedTemporaryChannel PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_TemporaryChannel)
143 | PermissionDeniedMissingCertificate PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_MissingCertificate)
144 | PermissionDeniedInvalidUserName PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_UserName)
145 | PermissionDeniedChannelFull PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_ChannelFull)
146 | PermissionDeniedNestingLimit PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_NestingLimit)
147 | PermissionDeniedChannelCountLimit PermissionDeniedType = PermissionDeniedType(MumbleProto.PermissionDenied_ChannelCountLimit)
148 | )
149 |
150 | // Has returns true if the PermissionDeniedType has changeType part of its
151 | // bitmask.
152 | func (p PermissionDeniedType) Has(changeType PermissionDeniedType) bool {
153 | return p&changeType == changeType
154 | }
155 |
156 | // PermissionDeniedEvent is the event that is passed to
157 | // EventListener.OnPermissionDenied.
158 | type PermissionDeniedEvent struct {
159 | Client *Client
160 | Type PermissionDeniedType
161 | Channel *Channel
162 | User *User
163 |
164 | Permission Permission
165 | String string
166 | }
167 |
168 | // UserListEvent is the event that is passed to EventListener.OnUserList.
169 | type UserListEvent struct {
170 | Client *Client
171 | UserList RegisteredUsers
172 | }
173 |
174 | // ACLEvent is the event that is passed to EventListener.OnACL.
175 | type ACLEvent struct {
176 | Client *Client
177 | ACL *ACL
178 | }
179 |
180 | // BanListEvent is the event that is passed to EventListener.OnBanList.
181 | type BanListEvent struct {
182 | Client *Client
183 | BanList BanList
184 | }
185 |
186 | // ContextActionChangeType specifies how a ContextAction changed.
187 | type ContextActionChangeType int
188 |
189 | // ContextAction change types.
190 | const (
191 | ContextActionAdd ContextActionChangeType = ContextActionChangeType(MumbleProto.ContextActionModify_Add)
192 | ContextActionRemove ContextActionChangeType = ContextActionChangeType(MumbleProto.ContextActionModify_Remove)
193 | )
194 |
195 | // ContextActionChangeEvent is the event that is passed to
196 | // EventListener.OnContextActionChange.
197 | type ContextActionChangeEvent struct {
198 | Client *Client
199 | Type ContextActionChangeType
200 | ContextAction *ContextAction
201 | }
202 |
203 | // ServerConfigEvent is the event that is passed to
204 | // EventListener.OnServerConfig.
205 | type ServerConfigEvent struct {
206 | Client *Client
207 |
208 | MaximumBitrate *int
209 | WelcomeMessage *string
210 | AllowHTML *bool
211 | MaximumMessageLength *int
212 | MaximumImageMessageLength *int
213 | MaximumUsers *int
214 |
215 | CodecAlpha *int32
216 | CodecBeta *int32
217 | CodecPreferAlpha *bool
218 | CodecOpus *bool
219 |
220 | SuggestVersion *Version
221 | SuggestPositional *bool
222 | SuggestPushToTalk *bool
223 | }
224 |
--------------------------------------------------------------------------------
/misc/gumble.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
100 |
--------------------------------------------------------------------------------
/gumble/client.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "crypto/tls"
5 | "errors"
6 | "math"
7 | "net"
8 | "runtime"
9 | "sync/atomic"
10 | "time"
11 |
12 | "github.com/golang/protobuf/proto"
13 | "layeh.com/gumble/gumble/MumbleProto"
14 | )
15 |
16 | // State is the current state of the client's connection to the server.
17 | type State int
18 |
19 | const (
20 | // StateDisconnected means the client is no longer connected to the server.
21 | StateDisconnected State = iota
22 |
23 | // StateConnected means the client is connected to the server and is
24 | // syncing initial information. This is an internal state that will
25 | // never be returned by Client.State().
26 | StateConnected
27 |
28 | // StateSynced means the client is connected to a server and has been sent
29 | // the server state.
30 | StateSynced
31 | )
32 |
33 | // ClientVersion is the protocol version that Client implements.
34 | const ClientVersion = 1<<16 | 3<<8 | 0
35 |
36 | // Client is the type used to create a connection to a server.
37 | type Client struct {
38 | // The User associated with the client.
39 | Self *User
40 | // The client's configuration.
41 | Config *Config
42 | // The underlying Conn to the server.
43 | Conn *Conn
44 |
45 | // The users currently connected to the server.
46 | Users Users
47 | // The connected server's channels.
48 | Channels Channels
49 | permissions map[uint32]*Permission
50 | tmpACL *ACL
51 |
52 | // Ping stats
53 | tcpPacketsReceived uint32
54 | tcpPingTimes [12]float32
55 | tcpPingAvg uint32
56 | tcpPingVar uint32
57 |
58 | // A collection containing the server's context actions.
59 | ContextActions ContextActions
60 |
61 | // The audio encoder used when sending audio to the server.
62 | AudioEncoder AudioEncoder
63 | audioCodec AudioCodec
64 | // To whom transmitted audio will be sent. The VoiceTarget must have already
65 | // been sent to the server for targeting to work correctly. Setting to nil
66 | // will disable voice targeting (i.e. switch back to regular speaking).
67 | VoiceTarget *VoiceTarget
68 |
69 | state uint32
70 |
71 | // volatile is held by the client when the internal data structures are being
72 | // modified.
73 | volatile rpwMutex
74 |
75 | connect chan *RejectError
76 | end chan struct{}
77 | disconnectEvent DisconnectEvent
78 | }
79 |
80 | // Dial is an alias of DialWithDialer(new(net.Dialer), addr, config, nil).
81 | func Dial(addr string, config *Config) (*Client, error) {
82 | return DialWithDialer(new(net.Dialer), addr, config, nil)
83 | }
84 |
85 | // DialWithDialer connects to the Mumble server at the given address.
86 | //
87 | // The function returns after the connection has been established, the initial
88 | // server information has been synced, and the OnConnect handlers have been
89 | // called.
90 | //
91 | // nil and an error is returned if server synchronization does not complete by
92 | // min(time.Now() + dialer.Timeout, dialer.Deadline), or if the server rejects
93 | // the client.
94 | func DialWithDialer(dialer *net.Dialer, addr string, config *Config, tlsConfig *tls.Config) (*Client, error) {
95 | start := time.Now()
96 |
97 | conn, err := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
98 | if err != nil {
99 | return nil, err
100 | }
101 |
102 | client := &Client{
103 | Conn: NewConn(conn),
104 | Config: config,
105 | Users: make(Users),
106 | Channels: make(Channels),
107 |
108 | permissions: make(map[uint32]*Permission),
109 |
110 | state: uint32(StateConnected),
111 |
112 | connect: make(chan *RejectError),
113 | end: make(chan struct{}),
114 | }
115 |
116 | go client.readRoutine()
117 |
118 | // Initial packets
119 | versionPacket := MumbleProto.Version{
120 | Version: proto.Uint32(ClientVersion),
121 | Release: proto.String("gumble"),
122 | Os: proto.String(runtime.GOOS),
123 | OsVersion: proto.String(runtime.GOARCH),
124 | }
125 | authenticationPacket := MumbleProto.Authenticate{
126 | Username: &client.Config.Username,
127 | Password: &client.Config.Password,
128 | Opus: proto.Bool(getAudioCodec(audioCodecIDOpus) != nil),
129 | Tokens: client.Config.Tokens,
130 | }
131 | client.Conn.WriteProto(&versionPacket)
132 | client.Conn.WriteProto(&authenticationPacket)
133 |
134 | go client.pingRoutine()
135 |
136 | var timeout <-chan time.Time
137 | {
138 | var deadline time.Time
139 | if !dialer.Deadline.IsZero() {
140 | deadline = dialer.Deadline
141 | }
142 | if dialer.Timeout > 0 {
143 | diff := start.Add(dialer.Timeout)
144 | if deadline.IsZero() || diff.Before(deadline) {
145 | deadline = diff
146 | }
147 | }
148 | if !deadline.IsZero() {
149 | timer := time.NewTimer(deadline.Sub(start))
150 | defer timer.Stop()
151 | timeout = timer.C
152 | }
153 | }
154 |
155 | select {
156 | case <-timeout:
157 | client.Conn.Close()
158 | return nil, errors.New("gumble: synchronization timeout")
159 | case err := <-client.connect:
160 | if err != nil {
161 | client.Conn.Close()
162 | return nil, err
163 | }
164 |
165 | return client, nil
166 | }
167 | }
168 |
169 | // State returns the current state of the client.
170 | func (c *Client) State() State {
171 | return State(atomic.LoadUint32(&c.state))
172 | }
173 |
174 | // AudioOutgoing creates a new channel that outgoing audio data can be written
175 | // to. The channel must be closed after the audio stream is completed. Only
176 | // a single channel should be open at any given time (i.e. close the channel
177 | // before opening another).
178 | func (c *Client) AudioOutgoing() chan<- AudioBuffer {
179 | ch := make(chan AudioBuffer)
180 | go func() {
181 | var seq int64
182 | previous := <-ch
183 | for p := range ch {
184 | previous.writeAudio(c, seq, false)
185 | previous = p
186 | seq = (seq + 1) % math.MaxInt32
187 | }
188 | if previous != nil {
189 | previous.writeAudio(c, seq, true)
190 | }
191 | }()
192 | return ch
193 | }
194 |
195 | // pingRoutine sends ping packets to the server at regular intervals.
196 | func (c *Client) pingRoutine() {
197 | ticker := time.NewTicker(time.Second * 5)
198 | defer ticker.Stop()
199 |
200 | var timestamp uint64
201 | var tcpPingAvg float32
202 | var tcpPingVar float32
203 | packet := MumbleProto.Ping{
204 | Timestamp: ×tamp,
205 | TcpPackets: &c.tcpPacketsReceived,
206 | TcpPingAvg: &tcpPingAvg,
207 | TcpPingVar: &tcpPingVar,
208 | }
209 |
210 | t := time.Now()
211 | for {
212 | timestamp = uint64(t.UnixNano())
213 | tcpPingAvg = math.Float32frombits(atomic.LoadUint32(&c.tcpPingAvg))
214 | tcpPingVar = math.Float32frombits(atomic.LoadUint32(&c.tcpPingVar))
215 | c.Conn.WriteProto(&packet)
216 |
217 | select {
218 | case <-c.end:
219 | return
220 | case t = <-ticker.C:
221 | // continue to top of loop
222 | }
223 | }
224 | }
225 |
226 | // readRoutine reads protocol buffer messages from the server.
227 | func (c *Client) readRoutine() {
228 | c.disconnectEvent = DisconnectEvent{
229 | Client: c,
230 | Type: DisconnectError,
231 | }
232 |
233 | for {
234 | pType, data, err := c.Conn.ReadPacket()
235 | if err != nil {
236 | break
237 | }
238 | if int(pType) < len(handlers) {
239 | handlers[pType](c, data)
240 | }
241 | }
242 |
243 | wasSynced := c.State() == StateSynced
244 | atomic.StoreUint32(&c.state, uint32(StateDisconnected))
245 | close(c.end)
246 | if wasSynced {
247 | c.Config.Listeners.onDisconnect(&c.disconnectEvent)
248 | }
249 | }
250 |
251 | // RequestUserList requests that the server's registered user list be sent to
252 | // the client.
253 | func (c *Client) RequestUserList() {
254 | packet := MumbleProto.UserList{}
255 | c.Conn.WriteProto(&packet)
256 | }
257 |
258 | // RequestBanList requests that the server's ban list be sent to the client.
259 | func (c *Client) RequestBanList() {
260 | packet := MumbleProto.BanList{
261 | Query: proto.Bool(true),
262 | }
263 | c.Conn.WriteProto(&packet)
264 | }
265 |
266 | // Disconnect disconnects the client from the server.
267 | func (c *Client) Disconnect() error {
268 | if c.State() == StateDisconnected {
269 | return errors.New("gumble: client is already disconnected")
270 | }
271 | c.disconnectEvent.Type = DisconnectUser
272 | c.Conn.Close()
273 | return nil
274 | }
275 |
276 | // Do executes f in a thread-safe manner. It ensures that Client and its
277 | // associated data will not be changed during the lifetime of the function
278 | // call.
279 | func (c *Client) Do(f func()) {
280 | c.volatile.RLock()
281 | defer c.volatile.RUnlock()
282 |
283 | f()
284 | }
285 |
286 | // Send will send a Message to the server.
287 | func (c *Client) Send(message Message) {
288 | message.writeMessage(c)
289 | }
290 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/gumble/handlers.go:
--------------------------------------------------------------------------------
1 | package gumble
2 |
3 | import (
4 | "crypto/x509"
5 | "encoding/binary"
6 | "errors"
7 | "math"
8 | "net"
9 | "sync/atomic"
10 | "time"
11 |
12 | "github.com/golang/protobuf/proto"
13 | "layeh.com/gumble/gumble/MumbleProto"
14 | "layeh.com/gumble/gumble/varint"
15 | )
16 |
17 | var (
18 | errUnimplementedHandler = errors.New("gumble: the handler has not been implemented")
19 | errIncompleteProtobuf = errors.New("gumble: protobuf message is missing a required field")
20 | errInvalidProtobuf = errors.New("gumble: protobuf message has an invalid field")
21 | errUnsupportedAudio = errors.New("gumble: unsupported audio codec")
22 | errNoCodec = errors.New("gumble: no audio codec")
23 | )
24 |
25 | var handlers = [...]func(*Client, []byte) error{
26 | (*Client).handleVersion,
27 | (*Client).handleUDPTunnel,
28 | (*Client).handleAuthenticate,
29 | (*Client).handlePing,
30 | (*Client).handleReject,
31 | (*Client).handleServerSync,
32 | (*Client).handleChannelRemove,
33 | (*Client).handleChannelState,
34 | (*Client).handleUserRemove,
35 | (*Client).handleUserState,
36 | (*Client).handleBanList,
37 | (*Client).handleTextMessage,
38 | (*Client).handlePermissionDenied,
39 | (*Client).handleACL,
40 | (*Client).handleQueryUsers,
41 | (*Client).handleCryptSetup,
42 | (*Client).handleContextActionModify,
43 | (*Client).handleContextAction,
44 | (*Client).handleUserList,
45 | (*Client).handleVoiceTarget,
46 | (*Client).handlePermissionQuery,
47 | (*Client).handleCodecVersion,
48 | (*Client).handleUserStats,
49 | (*Client).handleRequestBlob,
50 | (*Client).handleServerConfig,
51 | (*Client).handleSuggestConfig,
52 | }
53 |
54 | func parseVersion(packet *MumbleProto.Version) Version {
55 | var version Version
56 | if packet.Version != nil {
57 | version.Version = *packet.Version
58 | }
59 | if packet.Release != nil {
60 | version.Release = *packet.Release
61 | }
62 | if packet.Os != nil {
63 | version.OS = *packet.Os
64 | }
65 | if packet.OsVersion != nil {
66 | version.OSVersion = *packet.OsVersion
67 | }
68 | return version
69 | }
70 |
71 | func (c *Client) handleVersion(buffer []byte) error {
72 | var packet MumbleProto.Version
73 | if err := proto.Unmarshal(buffer, &packet); err != nil {
74 | return err
75 | }
76 | return nil
77 | }
78 |
79 | func (c *Client) handleUDPTunnel(buffer []byte) error {
80 | if len(buffer) < 1 {
81 | return errInvalidProtobuf
82 | }
83 | audioType := (buffer[0] >> 5) & 0x7
84 | audioTarget := buffer[0] & 0x1F
85 |
86 | // Opus only
87 | // TODO: add handling for other packet types
88 | if audioType != audioCodecIDOpus {
89 | return errUnsupportedAudio
90 | }
91 |
92 | // Session
93 | buffer = buffer[1:]
94 | session, n := varint.Decode(buffer)
95 | if n <= 0 {
96 | return errInvalidProtobuf
97 | }
98 | buffer = buffer[n:]
99 | user := c.Users[uint32(session)]
100 | if user == nil {
101 | return errInvalidProtobuf
102 | }
103 | decoder := user.decoder
104 | if decoder == nil {
105 | // TODO: decoder pool
106 | // TODO: de-reference after stream is done
107 | codec := c.audioCodec
108 | if codec == nil {
109 | return errNoCodec
110 | }
111 | decoder = codec.NewDecoder()
112 | user.decoder = decoder
113 | }
114 |
115 | // Sequence
116 | // TODO: use in jitter buffer
117 | _, n = varint.Decode(buffer)
118 | if n <= 0 {
119 | return errInvalidProtobuf
120 | }
121 | buffer = buffer[n:]
122 |
123 | // Length
124 | length, n := varint.Decode(buffer)
125 | if n <= 0 {
126 | return errInvalidProtobuf
127 | }
128 | buffer = buffer[n:]
129 | // Opus audio packets set the 13th bit in the size field as the terminator.
130 | audioLength := int(length) &^ 0x2000
131 | if audioLength > len(buffer) {
132 | return errInvalidProtobuf
133 | }
134 |
135 | pcm, err := decoder.Decode(buffer[:audioLength], AudioMaximumFrameSize)
136 | if err != nil {
137 | return err
138 | }
139 |
140 | event := AudioPacket{
141 | Client: c,
142 | Sender: user,
143 | Target: &VoiceTarget{
144 | ID: uint32(audioTarget),
145 | },
146 | AudioBuffer: AudioBuffer(pcm),
147 | }
148 |
149 | if len(buffer)-audioLength == 3*4 {
150 | // the packet has positional audio data; 3x float32
151 | buffer = buffer[audioLength:]
152 |
153 | event.X = math.Float32frombits(binary.LittleEndian.Uint32(buffer))
154 | event.Y = math.Float32frombits(binary.LittleEndian.Uint32(buffer[4:]))
155 | event.Z = math.Float32frombits(binary.LittleEndian.Uint32(buffer[8:]))
156 | event.HasPosition = true
157 | }
158 |
159 | c.volatile.Lock()
160 | for item := c.Config.AudioListeners.head; item != nil; item = item.next {
161 | c.volatile.Unlock()
162 | ch := item.streams[user]
163 | if ch == nil {
164 | ch = make(chan *AudioPacket)
165 | item.streams[user] = ch
166 | event := AudioStreamEvent{
167 | Client: c,
168 | User: user,
169 | C: ch,
170 | }
171 | item.listener.OnAudioStream(&event)
172 | }
173 | ch <- &event
174 | c.volatile.Lock()
175 | }
176 | c.volatile.Unlock()
177 |
178 | return nil
179 | }
180 |
181 | func (c *Client) handleAuthenticate(buffer []byte) error {
182 | return errUnimplementedHandler
183 | }
184 |
185 | func (c *Client) handlePing(buffer []byte) error {
186 | var packet MumbleProto.Ping
187 | if err := proto.Unmarshal(buffer, &packet); err != nil {
188 | return err
189 | }
190 |
191 | atomic.AddUint32(&c.tcpPacketsReceived, 1)
192 |
193 | if packet.Timestamp != nil {
194 | diff := time.Since(time.Unix(0, int64(*packet.Timestamp)))
195 |
196 | index := int(c.tcpPacketsReceived) - 1
197 | if index >= len(c.tcpPingTimes) {
198 | for i := 1; i < len(c.tcpPingTimes); i++ {
199 | c.tcpPingTimes[i-1] = c.tcpPingTimes[i]
200 | }
201 | index = len(c.tcpPingTimes) - 1
202 | }
203 |
204 | // average is in milliseconds
205 | ping := float32(diff.Seconds() * 1000)
206 | c.tcpPingTimes[index] = ping
207 |
208 | var sum float32
209 | for i := 0; i <= index; i++ {
210 | sum += c.tcpPingTimes[i]
211 | }
212 | avg := sum / float32(index+1)
213 |
214 | sum = 0
215 | for i := 0; i <= index; i++ {
216 | sum += (avg - c.tcpPingTimes[i]) * (avg - c.tcpPingTimes[i])
217 | }
218 | variance := sum / float32(index+1)
219 |
220 | atomic.StoreUint32(&c.tcpPingAvg, math.Float32bits(avg))
221 | atomic.StoreUint32(&c.tcpPingVar, math.Float32bits(variance))
222 | }
223 | return nil
224 | }
225 |
226 | func (c *Client) handleReject(buffer []byte) error {
227 | var packet MumbleProto.Reject
228 | if err := proto.Unmarshal(buffer, &packet); err != nil {
229 | return err
230 | }
231 |
232 | if c.State() != StateConnected {
233 | return errInvalidProtobuf
234 | }
235 |
236 | err := &RejectError{}
237 |
238 | if packet.Type != nil {
239 | err.Type = RejectType(*packet.Type)
240 | }
241 | if packet.Reason != nil {
242 | err.Reason = *packet.Reason
243 | }
244 | c.connect <- err
245 | c.Conn.Close()
246 | return nil
247 | }
248 |
249 | func (c *Client) handleServerSync(buffer []byte) error {
250 | var packet MumbleProto.ServerSync
251 | if err := proto.Unmarshal(buffer, &packet); err != nil {
252 | return err
253 | }
254 | event := ConnectEvent{
255 | Client: c,
256 | }
257 |
258 | if packet.Session != nil {
259 | {
260 | c.volatile.Lock()
261 |
262 | c.Self = c.Users[*packet.Session]
263 |
264 | c.volatile.Unlock()
265 | }
266 | }
267 | if packet.WelcomeText != nil {
268 | event.WelcomeMessage = packet.WelcomeText
269 | }
270 | if packet.MaxBandwidth != nil {
271 | val := int(*packet.MaxBandwidth)
272 | event.MaximumBitrate = &val
273 | }
274 | atomic.StoreUint32(&c.state, uint32(StateSynced))
275 | c.Config.Listeners.onConnect(&event)
276 | close(c.connect)
277 | return nil
278 | }
279 |
280 | func (c *Client) handleChannelRemove(buffer []byte) error {
281 | var packet MumbleProto.ChannelRemove
282 | if err := proto.Unmarshal(buffer, &packet); err != nil {
283 | return err
284 | }
285 |
286 | if packet.ChannelId == nil {
287 | return errIncompleteProtobuf
288 | }
289 |
290 | var channel *Channel
291 | {
292 | c.volatile.Lock()
293 |
294 | channelID := *packet.ChannelId
295 | channel = c.Channels[channelID]
296 | if channel == nil {
297 | c.volatile.Unlock()
298 | return errInvalidProtobuf
299 | }
300 | channel.client = nil
301 | delete(c.Channels, channelID)
302 | delete(c.permissions, channelID)
303 | if parent := channel.Parent; parent != nil {
304 | delete(parent.Children, channel.ID)
305 | }
306 | for _, link := range channel.Links {
307 | delete(link.Links, channelID)
308 | }
309 |
310 | c.volatile.Unlock()
311 | }
312 |
313 | if c.State() == StateSynced {
314 | event := ChannelChangeEvent{
315 | Client: c,
316 | Type: ChannelChangeRemoved,
317 | Channel: channel,
318 | }
319 | c.Config.Listeners.onChannelChange(&event)
320 | }
321 | return nil
322 | }
323 |
324 | func (c *Client) handleChannelState(buffer []byte) error {
325 | var packet MumbleProto.ChannelState
326 | if err := proto.Unmarshal(buffer, &packet); err != nil {
327 | return err
328 | }
329 |
330 | if packet.ChannelId == nil {
331 | return errIncompleteProtobuf
332 | }
333 | event := ChannelChangeEvent{
334 | Client: c,
335 | }
336 |
337 | {
338 | c.volatile.Lock()
339 |
340 | channelID := *packet.ChannelId
341 | channel := c.Channels[channelID]
342 | if channel == nil {
343 | channel = c.Channels.create(channelID)
344 | channel.client = c
345 |
346 | event.Type |= ChannelChangeCreated
347 | }
348 | event.Channel = channel
349 | if packet.Parent != nil {
350 | if channel.Parent != nil {
351 | delete(channel.Parent.Children, channelID)
352 | }
353 | newParent := c.Channels[*packet.Parent]
354 | if newParent != channel.Parent {
355 | event.Type |= ChannelChangeMoved
356 | }
357 | channel.Parent = newParent
358 | if channel.Parent != nil {
359 | channel.Parent.Children[channel.ID] = channel
360 | }
361 | }
362 | if packet.Name != nil {
363 | if *packet.Name != channel.Name {
364 | event.Type |= ChannelChangeName
365 | }
366 | channel.Name = *packet.Name
367 | }
368 | if packet.Links != nil {
369 | channel.Links = make(Channels)
370 | event.Type |= ChannelChangeLinks
371 | for _, channelID := range packet.Links {
372 | if c := c.Channels[channelID]; c != nil {
373 | channel.Links[channelID] = c
374 | }
375 | }
376 | }
377 | for _, channelID := range packet.LinksAdd {
378 | if c := c.Channels[channelID]; c != nil {
379 | event.Type |= ChannelChangeLinks
380 | channel.Links[channelID] = c
381 | c.Links[channel.ID] = channel
382 | }
383 | }
384 | for _, channelID := range packet.LinksRemove {
385 | if c := c.Channels[channelID]; c != nil {
386 | event.Type |= ChannelChangeLinks
387 | delete(channel.Links, channelID)
388 | delete(c.Links, channel.ID)
389 | }
390 | }
391 | if packet.Description != nil {
392 | if *packet.Description != channel.Description {
393 | event.Type |= ChannelChangeDescription
394 | }
395 | channel.Description = *packet.Description
396 | channel.DescriptionHash = nil
397 | }
398 | if packet.Temporary != nil {
399 | channel.Temporary = *packet.Temporary
400 | }
401 | if packet.Position != nil {
402 | if *packet.Position != channel.Position {
403 | event.Type |= ChannelChangePosition
404 | }
405 | channel.Position = *packet.Position
406 | }
407 | if packet.DescriptionHash != nil {
408 | event.Type |= ChannelChangeDescription
409 | channel.DescriptionHash = packet.DescriptionHash
410 | channel.Description = ""
411 | }
412 | if packet.MaxUsers != nil {
413 | event.Type |= ChannelChangeMaxUsers
414 | channel.MaxUsers = *packet.MaxUsers
415 | }
416 |
417 | c.volatile.Unlock()
418 | }
419 |
420 | if c.State() == StateSynced {
421 | c.Config.Listeners.onChannelChange(&event)
422 | }
423 | return nil
424 | }
425 |
426 | func (c *Client) handleUserRemove(buffer []byte) error {
427 | var packet MumbleProto.UserRemove
428 | if err := proto.Unmarshal(buffer, &packet); err != nil {
429 | return err
430 | }
431 |
432 | if packet.Session == nil {
433 | return errIncompleteProtobuf
434 | }
435 | event := UserChangeEvent{
436 | Client: c,
437 | Type: UserChangeDisconnected,
438 | }
439 |
440 | {
441 | c.volatile.Lock()
442 |
443 | session := *packet.Session
444 | event.User = c.Users[session]
445 | if event.User == nil {
446 | c.volatile.Unlock()
447 | return errInvalidProtobuf
448 | }
449 | if packet.Actor != nil {
450 | event.Actor = c.Users[*packet.Actor]
451 | if event.Actor == nil {
452 | c.volatile.Unlock()
453 | return errInvalidProtobuf
454 | }
455 | event.Type |= UserChangeKicked
456 | }
457 |
458 | event.User.client = nil
459 | if event.User.Channel != nil {
460 | delete(event.User.Channel.Users, session)
461 | }
462 | delete(c.Users, session)
463 | if packet.Reason != nil {
464 | event.String = *packet.Reason
465 | }
466 | if packet.Ban != nil && *packet.Ban {
467 | event.Type |= UserChangeBanned
468 | }
469 | if event.User == c.Self {
470 | if packet.Ban != nil && *packet.Ban {
471 | c.disconnectEvent.Type = DisconnectBanned
472 | } else {
473 | c.disconnectEvent.Type = DisconnectKicked
474 | }
475 | }
476 |
477 | c.volatile.Unlock()
478 | }
479 |
480 | if c.State() == StateSynced {
481 | c.Config.Listeners.onUserChange(&event)
482 | }
483 | return nil
484 | }
485 |
486 | func (c *Client) handleUserState(buffer []byte) error {
487 | var packet MumbleProto.UserState
488 | if err := proto.Unmarshal(buffer, &packet); err != nil {
489 | return err
490 | }
491 |
492 | if packet.Session == nil {
493 | return errIncompleteProtobuf
494 | }
495 | event := UserChangeEvent{
496 | Client: c,
497 | }
498 | var user, actor *User
499 | {
500 | c.volatile.Lock()
501 |
502 | session := *packet.Session
503 | user = c.Users[session]
504 | if user == nil {
505 | user = c.Users.create(session)
506 | user.Channel = c.Channels[0]
507 | user.client = c
508 |
509 | event.Type |= UserChangeConnected
510 |
511 | if user.Channel == nil {
512 | c.volatile.Unlock()
513 | return errInvalidProtobuf
514 | }
515 | event.Type |= UserChangeChannel
516 | user.Channel.Users[session] = user
517 | }
518 |
519 | event.User = user
520 | if packet.Actor != nil {
521 | actor = c.Users[*packet.Actor]
522 | if actor == nil {
523 | c.volatile.Unlock()
524 | return errInvalidProtobuf
525 | }
526 | event.Actor = actor
527 | }
528 | if packet.Name != nil {
529 | if *packet.Name != user.Name {
530 | event.Type |= UserChangeName
531 | }
532 | user.Name = *packet.Name
533 | }
534 | if packet.UserId != nil {
535 | if *packet.UserId != user.UserID && !event.Type.Has(UserChangeConnected) {
536 | if *packet.UserId != math.MaxUint32 {
537 | event.Type |= UserChangeRegistered
538 | user.UserID = *packet.UserId
539 | } else {
540 | event.Type |= UserChangeUnregistered
541 | user.UserID = 0
542 | }
543 | } else {
544 | user.UserID = *packet.UserId
545 | }
546 | }
547 | if packet.ChannelId != nil {
548 | if user.Channel != nil {
549 | delete(user.Channel.Users, user.Session)
550 | }
551 | newChannel := c.Channels[*packet.ChannelId]
552 | if newChannel == nil {
553 | c.volatile.Lock()
554 | return errInvalidProtobuf
555 | }
556 | if newChannel != user.Channel {
557 | event.Type |= UserChangeChannel
558 | user.Channel = newChannel
559 | }
560 | user.Channel.Users[user.Session] = user
561 | }
562 | if packet.Mute != nil {
563 | if *packet.Mute != user.Muted {
564 | event.Type |= UserChangeAudio
565 | }
566 | user.Muted = *packet.Mute
567 | }
568 | if packet.Deaf != nil {
569 | if *packet.Deaf != user.Deafened {
570 | event.Type |= UserChangeAudio
571 | }
572 | user.Deafened = *packet.Deaf
573 | }
574 | if packet.Suppress != nil {
575 | if *packet.Suppress != user.Suppressed {
576 | event.Type |= UserChangeAudio
577 | }
578 | user.Suppressed = *packet.Suppress
579 | }
580 | if packet.SelfMute != nil {
581 | if *packet.SelfMute != user.SelfMuted {
582 | event.Type |= UserChangeAudio
583 | }
584 | user.SelfMuted = *packet.SelfMute
585 | }
586 | if packet.SelfDeaf != nil {
587 | if *packet.SelfDeaf != user.SelfDeafened {
588 | event.Type |= UserChangeAudio
589 | }
590 | user.SelfDeafened = *packet.SelfDeaf
591 | }
592 | if packet.Texture != nil {
593 | event.Type |= UserChangeTexture
594 | user.Texture = packet.Texture
595 | user.TextureHash = nil
596 | }
597 | if packet.Comment != nil {
598 | if *packet.Comment != user.Comment {
599 | event.Type |= UserChangeComment
600 | }
601 | user.Comment = *packet.Comment
602 | user.CommentHash = nil
603 | }
604 | if packet.Hash != nil {
605 | user.Hash = *packet.Hash
606 | }
607 | if packet.CommentHash != nil {
608 | event.Type |= UserChangeComment
609 | user.CommentHash = packet.CommentHash
610 | user.Comment = ""
611 | }
612 | if packet.TextureHash != nil {
613 | event.Type |= UserChangeTexture
614 | user.TextureHash = packet.TextureHash
615 | user.Texture = nil
616 | }
617 | if packet.PrioritySpeaker != nil {
618 | if *packet.PrioritySpeaker != user.PrioritySpeaker {
619 | event.Type |= UserChangePrioritySpeaker
620 | }
621 | user.PrioritySpeaker = *packet.PrioritySpeaker
622 | }
623 | if packet.Recording != nil {
624 | if *packet.Recording != user.Recording {
625 | event.Type |= UserChangeRecording
626 | }
627 | user.Recording = *packet.Recording
628 | }
629 |
630 | c.volatile.Unlock()
631 | }
632 |
633 | if c.State() == StateSynced {
634 | c.Config.Listeners.onUserChange(&event)
635 | }
636 | return nil
637 | }
638 |
639 | func (c *Client) handleBanList(buffer []byte) error {
640 | var packet MumbleProto.BanList
641 | if err := proto.Unmarshal(buffer, &packet); err != nil {
642 | return err
643 | }
644 |
645 | event := BanListEvent{
646 | Client: c,
647 | BanList: make(BanList, 0, len(packet.Bans)),
648 | }
649 |
650 | for _, banPacket := range packet.Bans {
651 | ban := &Ban{
652 | Address: net.IP(banPacket.Address),
653 | }
654 | if banPacket.Mask != nil {
655 | size := net.IPv4len * 8
656 | if len(ban.Address) == net.IPv6len {
657 | size = net.IPv6len * 8
658 | }
659 | ban.Mask = net.CIDRMask(int(*banPacket.Mask), size)
660 | }
661 | if banPacket.Name != nil {
662 | ban.Name = *banPacket.Name
663 | }
664 | if banPacket.Hash != nil {
665 | ban.Hash = *banPacket.Hash
666 | }
667 | if banPacket.Reason != nil {
668 | ban.Reason = *banPacket.Reason
669 | }
670 | if banPacket.Start != nil {
671 | ban.Start, _ = time.Parse(time.RFC3339, *banPacket.Start)
672 | }
673 | if banPacket.Duration != nil {
674 | ban.Duration = time.Duration(*banPacket.Duration) * time.Second
675 | }
676 | event.BanList = append(event.BanList, ban)
677 | }
678 |
679 | c.Config.Listeners.onBanList(&event)
680 | return nil
681 | }
682 |
683 | func (c *Client) handleTextMessage(buffer []byte) error {
684 | var packet MumbleProto.TextMessage
685 | if err := proto.Unmarshal(buffer, &packet); err != nil {
686 | return err
687 | }
688 |
689 | event := TextMessageEvent{
690 | Client: c,
691 | }
692 | if packet.Actor != nil {
693 | event.Sender = c.Users[*packet.Actor]
694 | }
695 | if packet.Session != nil {
696 | event.Users = make([]*User, 0, len(packet.Session))
697 | for _, session := range packet.Session {
698 | if user := c.Users[session]; user != nil {
699 | event.Users = append(event.Users, user)
700 | }
701 | }
702 | }
703 | if packet.ChannelId != nil {
704 | event.Channels = make([]*Channel, 0, len(packet.ChannelId))
705 | for _, id := range packet.ChannelId {
706 | if channel := c.Channels[id]; channel != nil {
707 | event.Channels = append(event.Channels, channel)
708 | }
709 | }
710 | }
711 | if packet.TreeId != nil {
712 | event.Trees = make([]*Channel, 0, len(packet.TreeId))
713 | for _, id := range packet.TreeId {
714 | if channel := c.Channels[id]; channel != nil {
715 | event.Trees = append(event.Trees, channel)
716 | }
717 | }
718 | }
719 | if packet.Message != nil {
720 | event.Message = *packet.Message
721 | }
722 |
723 | c.Config.Listeners.onTextMessage(&event)
724 | return nil
725 | }
726 |
727 | func (c *Client) handlePermissionDenied(buffer []byte) error {
728 | var packet MumbleProto.PermissionDenied
729 | if err := proto.Unmarshal(buffer, &packet); err != nil {
730 | return err
731 | }
732 |
733 | if packet.Type == nil || *packet.Type == MumbleProto.PermissionDenied_H9K {
734 | return errInvalidProtobuf
735 | }
736 |
737 | event := PermissionDeniedEvent{
738 | Client: c,
739 | Type: PermissionDeniedType(*packet.Type),
740 | }
741 | if packet.Reason != nil {
742 | event.String = *packet.Reason
743 | }
744 | if packet.Name != nil {
745 | event.String = *packet.Name
746 | }
747 | if packet.Session != nil {
748 | event.User = c.Users[*packet.Session]
749 | if event.User == nil {
750 | return errInvalidProtobuf
751 | }
752 | }
753 | if packet.ChannelId != nil {
754 | event.Channel = c.Channels[*packet.ChannelId]
755 | if event.Channel == nil {
756 | return errInvalidProtobuf
757 | }
758 | }
759 | if packet.Permission != nil {
760 | event.Permission = Permission(*packet.Permission)
761 | }
762 |
763 | c.Config.Listeners.onPermissionDenied(&event)
764 | return nil
765 | }
766 |
767 | func (c *Client) handleACL(buffer []byte) error {
768 | var packet MumbleProto.ACL
769 | if err := proto.Unmarshal(buffer, &packet); err != nil {
770 | return err
771 | }
772 |
773 | acl := &ACL{
774 | Inherits: packet.GetInheritAcls(),
775 | }
776 | if packet.ChannelId == nil {
777 | return errInvalidProtobuf
778 | }
779 | acl.Channel = c.Channels[*packet.ChannelId]
780 | if acl.Channel == nil {
781 | return errInvalidProtobuf
782 | }
783 |
784 | if packet.Groups != nil {
785 | acl.Groups = make([]*ACLGroup, 0, len(packet.Groups))
786 | for _, group := range packet.Groups {
787 | aclGroup := &ACLGroup{
788 | Name: *group.Name,
789 | Inherited: group.GetInherited(),
790 | InheritUsers: group.GetInherit(),
791 | Inheritable: group.GetInheritable(),
792 | }
793 | if group.Add != nil {
794 | aclGroup.UsersAdd = make(map[uint32]*ACLUser)
795 | for _, userID := range group.Add {
796 | aclGroup.UsersAdd[userID] = &ACLUser{
797 | UserID: userID,
798 | }
799 | }
800 | }
801 | if group.Remove != nil {
802 | aclGroup.UsersRemove = make(map[uint32]*ACLUser)
803 | for _, userID := range group.Remove {
804 | aclGroup.UsersRemove[userID] = &ACLUser{
805 | UserID: userID,
806 | }
807 | }
808 | }
809 | if group.InheritedMembers != nil {
810 | aclGroup.UsersInherited = make(map[uint32]*ACLUser)
811 | for _, userID := range group.InheritedMembers {
812 | aclGroup.UsersInherited[userID] = &ACLUser{
813 | UserID: userID,
814 | }
815 | }
816 | }
817 | acl.Groups = append(acl.Groups, aclGroup)
818 | }
819 | }
820 | if packet.Acls != nil {
821 | acl.Rules = make([]*ACLRule, 0, len(packet.Acls))
822 | for _, rule := range packet.Acls {
823 | aclRule := &ACLRule{
824 | AppliesCurrent: rule.GetApplyHere(),
825 | AppliesChildren: rule.GetApplySubs(),
826 | Inherited: rule.GetInherited(),
827 | Granted: Permission(rule.GetGrant()),
828 | Denied: Permission(rule.GetDeny()),
829 | }
830 | if rule.UserId != nil {
831 | aclRule.User = &ACLUser{
832 | UserID: *rule.UserId,
833 | }
834 | } else if rule.Group != nil {
835 | var group *ACLGroup
836 | for _, g := range acl.Groups {
837 | if g.Name == *rule.Group {
838 | group = g
839 | break
840 | }
841 | }
842 | if group == nil {
843 | group = &ACLGroup{
844 | Name: *rule.Group,
845 | }
846 | }
847 | aclRule.Group = group
848 | }
849 | acl.Rules = append(acl.Rules, aclRule)
850 | }
851 | }
852 | c.tmpACL = acl
853 | return nil
854 | }
855 |
856 | func (c *Client) handleQueryUsers(buffer []byte) error {
857 | var packet MumbleProto.QueryUsers
858 | if err := proto.Unmarshal(buffer, &packet); err != nil {
859 | return err
860 | }
861 |
862 | acl := c.tmpACL
863 | if acl == nil {
864 | return errIncompleteProtobuf
865 | }
866 | c.tmpACL = nil
867 |
868 | userMap := make(map[uint32]string)
869 | for i := 0; i < len(packet.Ids) && i < len(packet.Names); i++ {
870 | userMap[packet.Ids[i]] = packet.Names[i]
871 | }
872 |
873 | for _, group := range acl.Groups {
874 | for _, user := range group.UsersAdd {
875 | user.Name = userMap[user.UserID]
876 | }
877 | for _, user := range group.UsersRemove {
878 | user.Name = userMap[user.UserID]
879 | }
880 | for _, user := range group.UsersInherited {
881 | user.Name = userMap[user.UserID]
882 | }
883 | }
884 | for _, rule := range acl.Rules {
885 | if rule.User != nil {
886 | rule.User.Name = userMap[rule.User.UserID]
887 | }
888 | }
889 |
890 | event := ACLEvent{
891 | Client: c,
892 | ACL: acl,
893 | }
894 | c.Config.Listeners.onACL(&event)
895 | return nil
896 | }
897 |
898 | func (c *Client) handleCryptSetup(buffer []byte) error {
899 | return errUnimplementedHandler
900 | }
901 |
902 | func (c *Client) handleContextActionModify(buffer []byte) error {
903 | var packet MumbleProto.ContextActionModify
904 | if err := proto.Unmarshal(buffer, &packet); err != nil {
905 | return err
906 | }
907 |
908 | if packet.Action == nil || packet.Operation == nil {
909 | return errInvalidProtobuf
910 | }
911 |
912 | event := ContextActionChangeEvent{
913 | Client: c,
914 | }
915 |
916 | {
917 | c.volatile.Lock()
918 |
919 | switch *packet.Operation {
920 | case MumbleProto.ContextActionModify_Add:
921 | if ca := c.ContextActions[*packet.Action]; ca != nil {
922 | c.volatile.Unlock()
923 | return nil
924 | }
925 | event.Type = ContextActionAdd
926 | contextAction := c.ContextActions.create(*packet.Action)
927 | if packet.Text != nil {
928 | contextAction.Label = *packet.Text
929 | }
930 | if packet.Context != nil {
931 | contextAction.Type = ContextActionType(*packet.Context)
932 | }
933 | event.ContextAction = contextAction
934 | case MumbleProto.ContextActionModify_Remove:
935 | contextAction := c.ContextActions[*packet.Action]
936 | if contextAction == nil {
937 | c.volatile.Unlock()
938 | return nil
939 | }
940 | event.Type = ContextActionRemove
941 | delete(c.ContextActions, *packet.Action)
942 | event.ContextAction = contextAction
943 | default:
944 | c.volatile.Unlock()
945 | return errInvalidProtobuf
946 | }
947 |
948 | c.volatile.Unlock()
949 | }
950 |
951 | c.Config.Listeners.onContextActionChange(&event)
952 | return nil
953 | }
954 |
955 | func (c *Client) handleContextAction(buffer []byte) error {
956 | return errUnimplementedHandler
957 | }
958 |
959 | func (c *Client) handleUserList(buffer []byte) error {
960 | var packet MumbleProto.UserList
961 | if err := proto.Unmarshal(buffer, &packet); err != nil {
962 | return err
963 | }
964 |
965 | event := UserListEvent{
966 | Client: c,
967 | UserList: make(RegisteredUsers, 0, len(packet.Users)),
968 | }
969 |
970 | for _, user := range packet.Users {
971 | registeredUser := &RegisteredUser{
972 | UserID: *user.UserId,
973 | }
974 | if user.Name != nil {
975 | registeredUser.Name = *user.Name
976 | }
977 | if user.LastSeen != nil {
978 | registeredUser.LastSeen, _ = time.ParseInLocation(time.RFC3339, *user.LastSeen, nil)
979 | }
980 | if user.LastChannel != nil {
981 | if lastChannel := c.Channels[*user.LastChannel]; lastChannel != nil {
982 | registeredUser.LastChannel = lastChannel
983 | }
984 | }
985 | event.UserList = append(event.UserList, registeredUser)
986 | }
987 |
988 | c.Config.Listeners.onUserList(&event)
989 | return nil
990 | }
991 |
992 | func (c *Client) handleVoiceTarget(buffer []byte) error {
993 | return errUnimplementedHandler
994 | }
995 |
996 | func (c *Client) handlePermissionQuery(buffer []byte) error {
997 | var packet MumbleProto.PermissionQuery
998 | if err := proto.Unmarshal(buffer, &packet); err != nil {
999 | return err
1000 | }
1001 |
1002 | var singleChannel *Channel
1003 | if packet.ChannelId != nil && packet.Permissions != nil {
1004 | singleChannel = c.Channels[*packet.ChannelId]
1005 | if singleChannel == nil {
1006 | return errInvalidProtobuf
1007 | }
1008 | }
1009 |
1010 | var changedChannels []*Channel
1011 |
1012 | {
1013 | c.volatile.Lock()
1014 |
1015 | if packet.GetFlush() {
1016 | oldPermissions := c.permissions
1017 | c.permissions = make(map[uint32]*Permission)
1018 | changedChannels = make([]*Channel, 0, len(oldPermissions))
1019 | for channelID := range oldPermissions {
1020 | changedChannels = append(changedChannels, c.Channels[channelID])
1021 | }
1022 | }
1023 |
1024 | if singleChannel != nil {
1025 | p := Permission(*packet.Permissions)
1026 | c.permissions[singleChannel.ID] = &p
1027 | changedChannels = append(changedChannels, singleChannel)
1028 | }
1029 |
1030 | c.volatile.Unlock()
1031 | }
1032 |
1033 | for _, channel := range changedChannels {
1034 | event := ChannelChangeEvent{
1035 | Client: c,
1036 | Type: ChannelChangePermission,
1037 | Channel: channel,
1038 | }
1039 | c.Config.Listeners.onChannelChange(&event)
1040 | }
1041 |
1042 | return nil
1043 | }
1044 |
1045 | func (c *Client) handleCodecVersion(buffer []byte) error {
1046 | var packet MumbleProto.CodecVersion
1047 | if err := proto.Unmarshal(buffer, &packet); err != nil {
1048 | return err
1049 | }
1050 | event := ServerConfigEvent{
1051 | Client: c,
1052 | }
1053 | event.CodecAlpha = packet.Alpha
1054 | event.CodecBeta = packet.Beta
1055 | {
1056 | val := packet.GetPreferAlpha()
1057 | event.CodecPreferAlpha = &val
1058 | }
1059 | {
1060 | val := packet.GetOpus()
1061 | event.CodecOpus = &val
1062 | }
1063 |
1064 | var codec AudioCodec
1065 | switch {
1066 | case *event.CodecOpus:
1067 | codec = getAudioCodec(audioCodecIDOpus)
1068 | }
1069 | if codec != nil {
1070 | c.audioCodec = codec
1071 |
1072 | {
1073 | c.volatile.Lock()
1074 |
1075 | c.AudioEncoder = codec.NewEncoder()
1076 |
1077 | c.volatile.Unlock()
1078 | }
1079 | }
1080 |
1081 | c.Config.Listeners.onServerConfig(&event)
1082 | return nil
1083 | }
1084 |
1085 | func (c *Client) handleUserStats(buffer []byte) error {
1086 | var packet MumbleProto.UserStats
1087 | if err := proto.Unmarshal(buffer, &packet); err != nil {
1088 | return err
1089 | }
1090 |
1091 | if packet.Session == nil {
1092 | return errIncompleteProtobuf
1093 | }
1094 | user := c.Users[*packet.Session]
1095 | if user == nil {
1096 | return errInvalidProtobuf
1097 | }
1098 |
1099 | {
1100 | c.volatile.Lock()
1101 |
1102 | if user.Stats == nil {
1103 | user.Stats = &UserStats{}
1104 | }
1105 | *user.Stats = UserStats{
1106 | User: user,
1107 | }
1108 | stats := user.Stats
1109 |
1110 | if packet.FromClient != nil {
1111 | if packet.FromClient.Good != nil {
1112 | stats.FromClient.Good = *packet.FromClient.Good
1113 | }
1114 | if packet.FromClient.Late != nil {
1115 | stats.FromClient.Late = *packet.FromClient.Late
1116 | }
1117 | if packet.FromClient.Lost != nil {
1118 | stats.FromClient.Lost = *packet.FromClient.Lost
1119 | }
1120 | if packet.FromClient.Resync != nil {
1121 | stats.FromClient.Resync = *packet.FromClient.Resync
1122 | }
1123 | }
1124 | if packet.FromServer != nil {
1125 | if packet.FromServer.Good != nil {
1126 | stats.FromServer.Good = *packet.FromServer.Good
1127 | }
1128 | if packet.FromClient.Late != nil {
1129 | stats.FromServer.Late = *packet.FromServer.Late
1130 | }
1131 | if packet.FromClient.Lost != nil {
1132 | stats.FromServer.Lost = *packet.FromServer.Lost
1133 | }
1134 | if packet.FromClient.Resync != nil {
1135 | stats.FromServer.Resync = *packet.FromServer.Resync
1136 | }
1137 | }
1138 |
1139 | if packet.UdpPackets != nil {
1140 | stats.UDPPackets = *packet.UdpPackets
1141 | }
1142 | if packet.UdpPingAvg != nil {
1143 | stats.UDPPingAverage = *packet.UdpPingAvg
1144 | }
1145 | if packet.UdpPingVar != nil {
1146 | stats.UDPPingVariance = *packet.UdpPingVar
1147 | }
1148 | if packet.TcpPackets != nil {
1149 | stats.TCPPackets = *packet.TcpPackets
1150 | }
1151 | if packet.TcpPingAvg != nil {
1152 | stats.TCPPingAverage = *packet.TcpPingAvg
1153 | }
1154 | if packet.TcpPingVar != nil {
1155 | stats.TCPPingVariance = *packet.TcpPingVar
1156 | }
1157 |
1158 | if packet.Version != nil {
1159 | stats.Version = parseVersion(packet.Version)
1160 | }
1161 | if packet.Onlinesecs != nil {
1162 | stats.Connected = time.Now().Add(time.Duration(*packet.Onlinesecs) * -time.Second)
1163 | }
1164 | if packet.Idlesecs != nil {
1165 | stats.Idle = time.Duration(*packet.Idlesecs) * time.Second
1166 | }
1167 | if packet.Bandwidth != nil {
1168 | stats.Bandwidth = int(*packet.Bandwidth)
1169 | }
1170 | if packet.Address != nil {
1171 | stats.IP = net.IP(packet.Address)
1172 | }
1173 | if packet.Certificates != nil {
1174 | stats.Certificates = make([]*x509.Certificate, 0, len(packet.Certificates))
1175 | for _, data := range packet.Certificates {
1176 | if data != nil {
1177 | if cert, err := x509.ParseCertificate(data); err == nil {
1178 | stats.Certificates = append(stats.Certificates, cert)
1179 | }
1180 | }
1181 | }
1182 | }
1183 | stats.StrongCertificate = packet.GetStrongCertificate()
1184 | stats.CELTVersions = packet.GetCeltVersions()
1185 | if packet.Opus != nil {
1186 | stats.Opus = *packet.Opus
1187 | }
1188 |
1189 | c.volatile.Unlock()
1190 | }
1191 |
1192 | event := UserChangeEvent{
1193 | Client: c,
1194 | Type: UserChangeStats,
1195 | User: user,
1196 | }
1197 |
1198 | c.Config.Listeners.onUserChange(&event)
1199 | return nil
1200 | }
1201 |
1202 | func (c *Client) handleRequestBlob(buffer []byte) error {
1203 | return errUnimplementedHandler
1204 | }
1205 |
1206 | func (c *Client) handleServerConfig(buffer []byte) error {
1207 | var packet MumbleProto.ServerConfig
1208 | if err := proto.Unmarshal(buffer, &packet); err != nil {
1209 | return err
1210 | }
1211 | event := ServerConfigEvent{
1212 | Client: c,
1213 | }
1214 | if packet.MaxBandwidth != nil {
1215 | val := int(*packet.MaxBandwidth)
1216 | event.MaximumBitrate = &val
1217 | }
1218 | if packet.WelcomeText != nil {
1219 | event.WelcomeMessage = packet.WelcomeText
1220 | }
1221 | if packet.AllowHtml != nil {
1222 | event.AllowHTML = packet.AllowHtml
1223 | }
1224 | if packet.MessageLength != nil {
1225 | val := int(*packet.MessageLength)
1226 | event.MaximumMessageLength = &val
1227 | }
1228 | if packet.ImageMessageLength != nil {
1229 | val := int(*packet.ImageMessageLength)
1230 | event.MaximumImageMessageLength = &val
1231 | }
1232 | if packet.MaxUsers != nil {
1233 | val := int(*packet.MaxUsers)
1234 | event.MaximumUsers = &val
1235 | }
1236 | c.Config.Listeners.onServerConfig(&event)
1237 | return nil
1238 | }
1239 |
1240 | func (c *Client) handleSuggestConfig(buffer []byte) error {
1241 | var packet MumbleProto.SuggestConfig
1242 | if err := proto.Unmarshal(buffer, &packet); err != nil {
1243 | return err
1244 | }
1245 | event := ServerConfigEvent{
1246 | Client: c,
1247 | }
1248 | if packet.Version != nil {
1249 | event.SuggestVersion = &Version{
1250 | Version: packet.GetVersion(),
1251 | }
1252 | }
1253 | if packet.Positional != nil {
1254 | event.SuggestPositional = packet.Positional
1255 | }
1256 | if packet.PushToTalk != nil {
1257 | event.SuggestPushToTalk = packet.PushToTalk
1258 | }
1259 | c.Config.Listeners.onServerConfig(&event)
1260 | return nil
1261 | }
1262 |
--------------------------------------------------------------------------------