From a42e4d07b4b923595a574aacedc10a0fb4e6be91 Mon Sep 17 00:00:00 2001 From: Mikkel Krautz Date: Sat, 12 Nov 2011 20:29:18 +0100 Subject: [PATCH] Add VoiceTarget support (whispers and shouts) --- Makefile | 1 + channel.go | 38 +++++++++++++ client.go | 16 ++++-- message.go | 61 ++++++++++++++++++-- server.go | 26 ++++++--- ssh.go | 2 +- voicetarget.go | 150 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 276 insertions(+), 18 deletions(-) create mode 100644 voicetarget.go diff --git a/Makefile b/Makefile index 0b142bb..25a1738 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,7 @@ GOFILES = \ acl.go \ group.go \ user.go \ + voicetarget.go \ freeze.go \ gencert.go \ register.go \ diff --git a/channel.go b/channel.go index e843f9b..a06e0dd 100644 --- a/channel.go +++ b/channel.go @@ -84,3 +84,41 @@ func (channel *Channel) DescriptionBlobHashBytes() (buf []byte) { } return buf } + +// Returns a slice of all channels in this channel's +// link chain. +func (channel *Channel) AllLinks() (seen map[int]*Channel) { + seen = make(map[int]*Channel) + walk := []*Channel{channel} + for len(walk) > 0 { + current := walk[len(walk)-1] + walk = walk[0 : len(walk)-1] + for _, linked := range current.Links { + if _, alreadySeen := seen[linked.Id]; !alreadySeen { + seen[linked.Id] = linked + walk = append(walk, linked) + } + } + } + return +} + +// Returns a slice of all of this channel's subchannels. +func (channel *Channel) AllSubChannels() (seen map[int]*Channel) { + seen = make(map[int]*Channel) + walk := []*Channel{} + if len(channel.children) > 0 { + walk = append(walk, channel) + for len(walk) > 0 { + current := walk[len(walk)-1] + walk = walk[0 : len(walk)-1] + for _, child := range current.children { + if _, alreadySeen := seen[child.Id]; !alreadySeen { + seen[child.Id] = child + walk = append(walk, child) + } + } + } + } + return +} diff --git a/client.go b/client.go index a6b3a2e..f632460 100644 --- a/client.go +++ b/client.go @@ -37,10 +37,11 @@ type Client struct { disconnected bool - lastResync int64 - crypt *cryptstate.CryptState - codecs []int32 - udp bool + lastResync int64 + crypt *cryptstate.CryptState + codecs []int32 + udp bool + voiceTargets map[uint32]*VoiceTarget // Ping stats UdpPingAvg float32 @@ -170,6 +171,13 @@ func (client *Client) ForceDisconnect() { client.disconnect(true) } +// Clear the client's caches +func (client *Client) ClearCaches() { + for _, vt := range client.voiceTargets { + vt.ClearCache() + } +} + // Reject an authentication attempt func (client *Client) RejectAuth(rejectType mumbleproto.Reject_RejectType, reason string) { var reasonString *string = nil diff --git a/message.go b/message.go index 6b56416..5ebfb92 100644 --- a/message.go +++ b/message.go @@ -287,7 +287,7 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) { channel.ACL = append(channel.ACL, acl) - server.ClearACLCache() + server.ClearCaches() } chanstate.ChannelId = proto.Uint32(uint32(channel.Id)) @@ -862,7 +862,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { } if userRegistrationChanged { - server.ClearACLCache() + server.ClearCaches() } err := server.broadcastProtoMessageWithPredicate(userstate, func(client *Client) bool { @@ -1207,8 +1207,8 @@ func (server *Server) handleAclMessage(client *Client, msg *Message) { channel.ACL = append(channel.ACL, chanacl) } - // Clear the server's ACL cache - server.ClearACLCache() + // Clear the Server's caches + server.ClearCaches() // Regular user? if !server.HasPermission(client, channel, WritePermission) && client.IsRegistered() || client.HasCertificate() { @@ -1226,7 +1226,7 @@ func (server *Server) handleAclMessage(client *Client, msg *Message) { channel.ACL = append(channel.ACL, chanacl) - server.ClearACLCache() + server.ClearCaches() } // Update freezer @@ -1374,6 +1374,57 @@ func (server *Server) handleUserStatsMessage(client *Client, msg *Message) { } } +// Voice target message +func (server *Server) handleVoiceTarget(client *Client, msg *Message) { + vt := &mumbleproto.VoiceTarget{} + err := proto.Unmarshal(msg.buf, vt) + if err != nil { + client.Panic(err.Error()) + return + } + + if vt.Id == nil { + return + } + + id := *vt.Id + if id < 1 || id >= 0x1f { + return + } + + if len(vt.Targets) == 0 { + delete(client.voiceTargets, id) + } + + for _, target := range vt.Targets { + newTarget := &VoiceTarget{} + for _, session := range target.Session { + newTarget.AddSession(session) + } + if target.ChannelId != nil { + chanid := *target.ChannelId + group := "" + links := false + subchannels := false + if target.Group != nil { + group = *target.Group + } + if target.Links != nil { + links = *target.Links + } + if target.Children != nil { + subchannels = *target.Children + } + newTarget.AddChannel(chanid, subchannels, links, group) + } + if newTarget.IsEmpty() { + delete(client.voiceTargets, id) + } else { + client.voiceTargets[id] = newTarget + } + } +} + // Permission query func (server *Server) handlePermissionQuery(client *Client, msg *Message) { query := &mumbleproto.PermissionQuery{} diff --git a/server.go b/server.go index 0780245..6f52bcf 100644 --- a/server.go +++ b/server.go @@ -249,6 +249,7 @@ func (server *Server) NewClient(conn net.Conn) (err error) { client.state = StateClientConnected client.udprecv = make(chan []byte) + client.voiceTargets = make(map[uint32]*VoiceTarget) client.user = nil @@ -345,8 +346,7 @@ func (server *Server) handlerLoop() { server.handleIncomingMessage(client, msg) // Voice broadcast case vb := <-server.voicebroadcast: - server.Printf("VoiceBroadcast!") - if vb.target == 0 { + if vb.target == 0 { // Current channel channel := vb.client.Channel for _, client := range channel.clients { if client != vb.client { @@ -356,6 +356,13 @@ func (server *Server) handlerLoop() { } } } + } else { + target, ok := vb.client.voiceTargets[uint32(vb.target)] + if !ok { + continue + } + + target.SendVoiceBroadcast(vb) } // Finish client authentication. Send post-authentication // server info. @@ -413,7 +420,7 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) { // by sending an Authenticate message with he contents of their new // access token list. client.Tokens = auth.Tokens - server.ClearACLCache() + server.ClearCaches() if client.state >= StateClientAuthenticated { return @@ -847,7 +854,7 @@ func (server *Server) handleIncomingMessage(client *Client, msg *Message) { case mumbleproto.MessageUserList: server.handleUserList(msg.client, msg) case mumbleproto.MessageVoiceTarget: - server.Printf("MessageVoiceTarget from client") + server.handleVoiceTarget(msg.client, msg) case mumbleproto.MessagePermissionQuery: server.handlePermissionQuery(msg.client, msg) case mumbleproto.MessageUserStats: @@ -958,9 +965,12 @@ func (server *Server) handleUdpPacket(udpaddr *net.UDPAddr, buf []byte, nread in match.udprecv <- plain } -// Clear the ACL cache -func (s *Server) ClearACLCache() { - s.aclcache = NewACLCache() +// Clear the Server's caches +func (server *Server) ClearCaches() { + server.aclcache = NewACLCache() + for _, client := range server.clients { + client.ClearCaches() + } } // Helper method for users entering new channels @@ -975,7 +985,7 @@ func (server *Server) userEnterChannel(client *Client, channel *Channel, usersta } channel.AddClient(client) - server.ClearACLCache() + server.ClearCaches() // fixme(mkrautz): Set LastChannel for user in datastore // fixme(mkrautz): Remove channel if temporary diff --git a/ssh.go b/ssh.go index 017f382..1819cc2 100644 --- a/ssh.go +++ b/ssh.go @@ -390,4 +390,4 @@ func ClearConfCmd(reply SshCmdReply, args []string) error { reply.WriteString(fmt.Sprintf("[%v] Cleared value for %v\r\n", serverId, key)) return nil -} \ No newline at end of file +} diff --git a/voicetarget.go b/voicetarget.go new file mode 100644 index 0000000..030d645 --- /dev/null +++ b/voicetarget.go @@ -0,0 +1,150 @@ +// Copyright (c) 2011 The Grumble Authors +// The use of this source code is goverened by a BSD-style +// license that can be found in the LICENSE-file. + +package main + +// A VoiceTarget holds information about a single +// VoiceTarget entry of a Client. +type VoiceTarget struct { + sessions []uint32 + channels []voiceTargetChannel + + directCache map[uint32]*Client + fromChannelsCache map[uint32]*Client +} + +type voiceTargetChannel struct { + id uint32 + subChannels bool + links bool + onlyGroup string +} + +// Add's a client's session to the VoiceTarget +func (vt *VoiceTarget) AddSession(session uint32) { + vt.sessions = append(vt.sessions, session) +} + +// Add a channel to the VoiceTarget. +// If subchannels is true, any sent voice packets will also be sent to all subchannels. +// If links is true, any sent voice packets will also be sent to all linked channels. +// If group is a non-empty string, any sent voice packets will only be broadcast to members +// of that group who reside in the channel (or its children or linked channels). +func (vt *VoiceTarget) AddChannel(id uint32, subchannels bool, links bool, group string) { + vt.channels = append(vt.channels, voiceTargetChannel{ + id: id, + subChannels: subchannels, + links: links, + onlyGroup: group, + }) +} + +// Checks whether the VoiceTarget is empty (has no targets) +func (vt *VoiceTarget) IsEmpty() bool { + return len(vt.sessions) == 0 && len(vt.channels) == 0 +} + +// Clear the VoiceTarget's cache. +func (vt *VoiceTarget) ClearCache() { + vt.directCache = nil + vt.fromChannelsCache = nil +} + +// Send the contents of the VoiceBroadcast to all targets specified in the +// VoiceTarget. +func (vt *VoiceTarget) SendVoiceBroadcast(vb *VoiceBroadcast) { + buf := vb.buf + client := vb.client + server := client.server + + direct := vt.directCache + fromChannels := vt.fromChannelsCache + + if direct == nil || fromChannels == nil { + direct = make(map[uint32]*Client) + fromChannels = make(map[uint32]*Client) + + for _, vtc := range vt.channels { + channel := server.Channels[int(vtc.id)] + if channel == nil { + continue + } + + if !vtc.subChannels && !vtc.links && vtc.onlyGroup == "" { + if server.HasPermission(client, channel, WhisperPermission) { + for _, target := range channel.clients { + fromChannels[target.Session] = target + } + } + } else { + server.Printf("%v", vtc) + newchans := make(map[int]*Channel) + if vtc.links { + newchans = channel.AllLinks() + } else { + newchans[channel.Id] = channel + } + if vtc.subChannels { + subchans := channel.AllSubChannels() + for k, v := range subchans { + newchans[k] = v + } + } + for _, newchan := range newchans { + if server.HasPermission(client, newchan, WhisperPermission) { + for _, target := range newchan.clients { + if vtc.onlyGroup == "" || GroupMemberCheck(newchan, newchan, vtc.onlyGroup, target) { + fromChannels[target.Session] = target + } + } + } + } + } + } + + for _, session := range vt.sessions { + target := server.clients[session] + if target != nil { + if _, alreadyInFromChannels := fromChannels[target.Session]; !alreadyInFromChannels { + direct[target.Session] = target + } + } + } + + // Make sure we don't send to ourselves. + delete(direct, client.Session) + delete(fromChannels, client.Session) + + if vt.directCache == nil { + vt.directCache = direct + } + + if vt.fromChannelsCache == nil { + vt.fromChannelsCache = fromChannels + } + } + + kind := buf[0] & 0xe0 + + if len(fromChannels) > 0 { + for _, target := range fromChannels { + buf[0] = kind | 2 + err := target.SendUDP(buf) + if err != nil { + target.Panicf("Unable to send UDP packet: %v", err.Error()) + } + } + } + + if len(direct) > 0 { + for _, target := range direct { + buf[0] = kind | 2 + target.SendUDP(buf) + err := target.SendUDP(buf) + if err != nil { + target.Panicf("Unable to send UDP packet: %v", err.Error()) + } + } + } +}