WebRTC audio/video call and conferencing server.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

326 lines
7.9 KiB

/*
* Spreed Speak Freely.
* Copyright (C) 2013-2014 struktur AG
*
* This file is part of Spreed Speak Freely.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/gorilla/securecookie"
"log"
"sync"
"sync/atomic"
"time"
)
const (
turnTTL = 3600 // XXX(longsleep): Add to config file.
maxBroadcastPerSecond = 1000
maxUsersLength = 5000
)
type MessageRequest struct {
From string
To string
Message Buffer
Id string
}
type HubStat struct {
Rooms int `json:"rooms"`
Connections int `json:"connections"`
Users int `json:"users"`
Count uint64 `json:"count"`
BroadcastChatMessages uint64 `json:"broadcastchatmessages"`
UnicastChatMessages uint64 `json:"unicastchatmessages"`
IdsInRoom map[string][]string `json:"idsinroom,omitempty"`
UsersById map[string]*DataUser `json:"usersbyid,omitempty"`
ConnectionsByIdx map[string]string `json:"connectionsbyidx,omitempty"`
}
type Hub struct {
server *Server
connectionTable map[string]*Connection
userTable map[string]*User
roomTable map[string]*RoomWorker
version string
config *Config
sessionSecret []byte
turnSecret []byte
tickets *securecookie.SecureCookie
count uint64
mutex sync.RWMutex
buffers BufferCache
broadcastChatMessages uint64
unicastChatMessages uint64
}
func NewHub(version string, config *Config, sessionSecret string, turnSecret string) *Hub {
h := &Hub{
connectionTable: make(map[string]*Connection),
userTable: make(map[string]*User),
roomTable: make(map[string]*RoomWorker),
version: version,
config: config,
sessionSecret: []byte(sessionSecret),
turnSecret: []byte(turnSecret),
}
h.tickets = securecookie.New(h.sessionSecret, nil)
h.buffers = NewBufferCache(1024, bytes.MinRead)
return h
}
func (h *Hub) Stat(details bool) *HubStat {
h.mutex.RLock()
defer h.mutex.RUnlock()
stat := &HubStat{
Rooms: len(h.roomTable),
Connections: len(h.connectionTable),
Users: len(h.userTable),
Count: h.count,
BroadcastChatMessages: atomic.LoadUint64(&h.broadcastChatMessages),
UnicastChatMessages: atomic.LoadUint64(&h.unicastChatMessages),
}
if details {
rooms := make(map[string][]string)
for roomid, room := range h.roomTable {
users := make([]string, 0, len(room.connections))
for id, _ := range room.connections {
users = append(users, id)
}
rooms[roomid] = users
}
stat.IdsInRoom = rooms
users := make(map[string]*DataUser)
for userid, user := range h.userTable {
users[userid] = user.Data()
}
stat.UsersById = users
connections := make(map[string]string)
for id, connection := range h.connectionTable {
connections[fmt.Sprintf("%d", connection.Idx)] = id
}
stat.ConnectionsByIdx = connections
}
return stat
}
func (h *Hub) CreateTurnData(id string) *DataTurn {
// Create turn data credentials for shared secret auth with TURN
// server. See http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
// and https://code.google.com/p/rfc5766-turn-server/ REST API auth
// and set shared secret in TURN server with static-auth-secret.
if len(h.turnSecret) == 0 {
return &DataTurn{}
}
foo := hmac.New(sha1.New, h.turnSecret)
user := fmt.Sprintf("%s:%d", id, int32(time.Now().Unix()))
foo.Write([]byte(user))
password := base64.StdEncoding.EncodeToString(foo.Sum(nil))
return &DataTurn{user, password, turnTTL, h.config.TurnURIs}
}
func (h *Hub) EncodeTicket(key, value string) (string, error) {
if value == "" {
// Create new id.
value = fmt.Sprintf("%s", securecookie.GenerateRandomKey(16))
}
return h.tickets.Encode(key, value)
}
func (h *Hub) DecodeTicket(key, value string) (string, error) {
result := ""
err := h.tickets.Decode(key, value, &result)
return result, err
}
func (h *Hub) GetRoom(id string) *RoomWorker {
h.mutex.RLock()
room, ok := h.roomTable[id]
if !ok {
h.mutex.RUnlock()
h.mutex.Lock()
// need to re-check, another thread might have created the room
// while we waited for the lock
room, ok = h.roomTable[id]
if !ok {
room = NewRoomWorker(h, id)
h.roomTable[id] = room
h.mutex.Unlock()
go func() {
// Start room, this blocks until room expired.
room.Start()
// Cleanup room when we are done.
h.mutex.Lock()
defer h.mutex.Unlock()
delete(h.roomTable, id)
log.Printf("Cleaned up room '%s'\n", id)
}()
} else {
h.mutex.Unlock()
}
} else {
h.mutex.RUnlock()
}
return room
}
func (h *Hub) GetGlobalConnections() []*Connection {
if h.config.globalRoomid == "" {
return make([]*Connection, 0)
}
h.mutex.RLock()
if room, ok := h.roomTable[h.config.globalRoomid]; ok {
h.mutex.RUnlock()
return room.GetConnections()
} else {
h.mutex.RUnlock()
}
return make([]*Connection, 0)
}
func (h *Hub) RunForAllRooms(f func(room *RoomWorker)) {
h.mutex.RLock()
for _, room := range h.roomTable {
f(room)
}
h.mutex.RUnlock()
}
func (h *Hub) isGlobalRoomid(id string) bool {
return id != "" && (id == h.config.globalRoomid)
}
func (h *Hub) isDefaultRoomid(id string) bool {
return id == ""
}
func (h *Hub) registerHandler(c *Connection) {
h.mutex.Lock()
// Create new user instance.
h.count++
c.Idx = h.count
u := &User{Id: c.Id}
h.userTable[c.Id] = u
c.User = u
c.IsRegistered = true
// Register connection or replace existing one.
if ec, ok := h.connectionTable[c.Id]; ok {
delete(h.connectionTable, ec.Id)
ec.IsRegistered = false
ec.close()
h.connectionTable[c.Id] = c
h.mutex.Unlock()
//log.Printf("Register (%d) from %s: %s (existing)\n", c.Idx, c.RemoteAddr, c.Id)
} else {
h.connectionTable[c.Id] = c
//fmt.Println("registered", c.Id)
h.mutex.Unlock()
//log.Printf("Register (%d) from %s: %s\n", c.Idx, c.RemoteAddr, c.Id)
h.server.OnRegister(c)
}
}
func (h *Hub) unregisterHandler(c *Connection) {
h.mutex.Lock()
if !c.IsRegistered {
h.mutex.Unlock()
return
}
c.close()
delete(h.connectionTable, c.Id)
delete(h.userTable, c.Id)
h.mutex.Unlock()
//log.Printf("Unregister (%d) from %s: %s\n", c.Idx, c.RemoteAddr, c.Id)
h.server.OnUnregister(c)
}
func (h *Hub) unicastHandler(m *MessageRequest) {
h.mutex.RLock()
out, ok := h.connectionTable[m.To]
h.mutex.RUnlock()
if !ok {
log.Println("Unicast To not found", m.To)
return
}
out.send(m.Message)
}
func (h *Hub) aliveHandler(c *Connection, alive *DataAlive) {
aliveJson := h.buffers.New()
encoder := json.NewEncoder(aliveJson)
err := encoder.Encode(&DataOutgoing{From: c.Id, Data: alive})
if err != nil {
log.Println("Alive error while encoding JSON", err)
aliveJson.Decref()
return
}
c.send(aliveJson)
aliveJson.Decref()
}
func (h *Hub) userupdateHandler(u *UserUpdate) uint64 {
//fmt.Println("Userupdate", u)
h.mutex.RLock()
user, ok := h.userTable[u.Id]
h.mutex.RUnlock()
var rev uint64
if ok {
rev = user.Update(u)
} else {
log.Printf("Update data for unknown user %s\n", u.Id)
}
return rev
}