2020-04-24 19:38:21 +02:00
|
|
|
// Copyright (c) 2020 by Juliusz Chroboczek.
|
|
|
|
|
|
|
|
// This is not open source software. Copy it, and I'll break into your
|
|
|
|
// house and tell your three year-old that Santa doesn't exist.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2020-04-30 20:15:52 +02:00
|
|
|
"sfu/estimator"
|
2020-04-27 21:43:29 +02:00
|
|
|
|
2020-04-24 19:38:21 +02:00
|
|
|
"github.com/gorilla/websocket"
|
2020-07-16 20:17:32 +02:00
|
|
|
"github.com/pion/webrtc/v3"
|
2020-04-24 19:38:21 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var iceConf webrtc.Configuration
|
|
|
|
var iceOnce sync.Once
|
|
|
|
|
|
|
|
func iceConfiguration() webrtc.Configuration {
|
|
|
|
iceOnce.Do(func() {
|
|
|
|
var iceServers []webrtc.ICEServer
|
|
|
|
file, err := os.Open(iceFilename)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Open %v: %v", iceFilename, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
d := json.NewDecoder(file)
|
|
|
|
err = d.Decode(&iceServers)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Get ICE configuration: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
iceConf = webrtc.Configuration{
|
|
|
|
ICEServers: iceServers,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return iceConf
|
|
|
|
}
|
|
|
|
|
|
|
|
type protocolError string
|
|
|
|
|
|
|
|
func (err protocolError) Error() string {
|
|
|
|
return string(err)
|
|
|
|
}
|
|
|
|
|
2020-04-25 02:09:11 +02:00
|
|
|
type userError string
|
|
|
|
|
|
|
|
func (err userError) Error() string {
|
|
|
|
return string(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func errorToWSCloseMessage(err error) (string, []byte) {
|
2020-04-24 19:38:21 +02:00
|
|
|
var code int
|
|
|
|
var text string
|
|
|
|
switch e := err.(type) {
|
|
|
|
case *websocket.CloseError:
|
|
|
|
code = websocket.CloseNormalClosure
|
|
|
|
case protocolError:
|
|
|
|
code = websocket.CloseProtocolError
|
|
|
|
text = string(e)
|
2020-04-25 02:09:11 +02:00
|
|
|
case userError:
|
|
|
|
code = websocket.CloseNormalClosure
|
|
|
|
text = string(e)
|
2020-04-24 19:38:21 +02:00
|
|
|
default:
|
|
|
|
code = websocket.CloseInternalServerErr
|
|
|
|
}
|
2020-04-26 19:15:02 +02:00
|
|
|
return text, websocket.FormatCloseMessage(code, text)
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func isWSNormalError(err error) bool {
|
|
|
|
return websocket.IsCloseError(err,
|
|
|
|
websocket.CloseNormalClosure,
|
|
|
|
websocket.CloseGoingAway)
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
type webClient struct {
|
|
|
|
group *group
|
|
|
|
id string
|
2020-08-12 11:28:49 +02:00
|
|
|
credentials clientCredentials
|
2020-09-13 10:16:10 +02:00
|
|
|
permissions clientPermissions
|
2020-05-27 11:44:49 +02:00
|
|
|
requested map[string]uint32
|
|
|
|
done chan struct{}
|
|
|
|
writeCh chan interface{}
|
|
|
|
writerDone chan struct{}
|
|
|
|
actionCh chan interface{}
|
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
down map[string]*rtpDownConnection
|
2020-06-08 22:14:28 +02:00
|
|
|
up map[string]*rtpUpConnection
|
2020-05-27 11:44:49 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
func (c *webClient) Group() *group {
|
2020-05-28 02:35:09 +02:00
|
|
|
return c.group
|
|
|
|
}
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
func (c *webClient) Id() string {
|
2020-05-28 02:35:09 +02:00
|
|
|
return c.id
|
|
|
|
}
|
|
|
|
|
2020-08-12 11:28:49 +02:00
|
|
|
func (c *webClient) Credentials() clientCredentials {
|
|
|
|
return c.credentials
|
2020-05-28 02:35:09 +02:00
|
|
|
}
|
|
|
|
|
2020-09-13 10:16:10 +02:00
|
|
|
func (c *webClient) SetPermissions(perms clientPermissions) {
|
|
|
|
c.permissions = perms
|
|
|
|
}
|
|
|
|
|
2020-05-31 20:41:17 +02:00
|
|
|
func (c *webClient) pushClient(id, username string, add bool) error {
|
2020-08-12 13:51:31 +02:00
|
|
|
kind := "add"
|
|
|
|
if !add {
|
|
|
|
kind = "delete"
|
|
|
|
}
|
2020-05-31 20:41:17 +02:00
|
|
|
return c.write(clientMessage{
|
|
|
|
Type: "user",
|
2020-08-12 13:51:31 +02:00
|
|
|
Kind: kind,
|
2020-05-31 20:41:17 +02:00
|
|
|
Id: id,
|
|
|
|
Username: username,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-05-18 00:35:58 +02:00
|
|
|
type rateMap map[string]uint32
|
|
|
|
|
|
|
|
func (v *rateMap) UnmarshalJSON(b []byte) error {
|
|
|
|
var m map[string]interface{}
|
|
|
|
|
|
|
|
err := json.Unmarshal(b, &m)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
n := make(map[string]uint32, len(m))
|
|
|
|
for k, w := range m {
|
|
|
|
switch w := w.(type) {
|
|
|
|
case bool:
|
|
|
|
if w {
|
|
|
|
n[k] = ^uint32(0)
|
|
|
|
} else {
|
|
|
|
n[k] = 0
|
|
|
|
}
|
|
|
|
case float64:
|
|
|
|
if w < 0 || w >= float64(^uint32(0)) {
|
|
|
|
return errors.New("overflow")
|
|
|
|
}
|
|
|
|
n[k] = uint32(w)
|
|
|
|
default:
|
|
|
|
return errors.New("unexpected type in JSON map")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*v = n
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v rateMap) MarshalJSON() ([]byte, error) {
|
|
|
|
m := make(map[string]interface{}, len(v))
|
|
|
|
for k, w := range v {
|
|
|
|
switch w {
|
|
|
|
case 0:
|
|
|
|
m[k] = false
|
|
|
|
case ^uint32(0):
|
|
|
|
m[k] = true
|
|
|
|
default:
|
|
|
|
m[k] = w
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return json.Marshal(m)
|
|
|
|
}
|
|
|
|
|
2020-04-24 19:38:21 +02:00
|
|
|
type clientMessage struct {
|
2020-04-25 02:09:11 +02:00
|
|
|
Type string `json:"type"`
|
2020-08-12 13:56:35 +02:00
|
|
|
Kind string `json:"kind,omitempty"`
|
2020-04-25 02:09:11 +02:00
|
|
|
Id string `json:"id,omitempty"`
|
|
|
|
Username string `json:"username,omitempty"`
|
|
|
|
Password string `json:"password,omitempty"`
|
2020-09-13 10:16:10 +02:00
|
|
|
Permissions clientPermissions `json:"permissions,omitempty"`
|
2020-04-25 02:09:11 +02:00
|
|
|
Group string `json:"group,omitempty"`
|
|
|
|
Value string `json:"value,omitempty"`
|
|
|
|
Offer *webrtc.SessionDescription `json:"offer,omitempty"`
|
|
|
|
Answer *webrtc.SessionDescription `json:"answer,omitempty"`
|
|
|
|
Candidate *webrtc.ICECandidateInit `json:"candidate,omitempty"`
|
2020-05-17 22:31:29 +02:00
|
|
|
Labels map[string]string `json:"labels,omitempty"`
|
2020-05-18 00:35:58 +02:00
|
|
|
Request rateMap `json:"request,omitempty"`
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type closeMessage struct {
|
|
|
|
data []byte
|
|
|
|
}
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
func getUpConn(c *webClient, id string) *rtpUpConnection {
|
2020-04-28 14:54:50 +02:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
2020-04-24 19:38:21 +02:00
|
|
|
|
|
|
|
if c.up == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2020-06-08 22:14:28 +02:00
|
|
|
return c.up[id]
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
func getUpConns(c *webClient) []*rtpUpConnection {
|
2020-04-28 14:54:50 +02:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
2020-06-08 22:14:28 +02:00
|
|
|
up := make([]*rtpUpConnection, 0, len(c.up))
|
2020-06-08 19:10:08 +02:00
|
|
|
for _, u := range c.up {
|
|
|
|
up = append(up, u)
|
2020-04-25 18:35:32 +02:00
|
|
|
}
|
|
|
|
return up
|
|
|
|
}
|
|
|
|
|
2020-08-06 23:55:00 +02:00
|
|
|
func addUpConn(c *webClient, id string) (*rtpUpConnection, bool, error) {
|
2020-04-28 23:41:18 +02:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
|
|
if c.up == nil {
|
2020-06-08 22:14:28 +02:00
|
|
|
c.up = make(map[string]*rtpUpConnection)
|
2020-04-28 23:41:18 +02:00
|
|
|
}
|
2020-06-10 20:25:25 +02:00
|
|
|
if c.down != nil && c.down[id] != nil {
|
2020-08-06 23:55:00 +02:00
|
|
|
return nil, false, errors.New("Adding duplicate connection")
|
2020-04-28 23:41:18 +02:00
|
|
|
}
|
2020-06-10 20:25:25 +02:00
|
|
|
|
|
|
|
old := c.up[id]
|
|
|
|
if old != nil {
|
2020-08-06 23:55:00 +02:00
|
|
|
return old, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
conn, err := newUpConn(c, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
2020-06-10 20:25:25 +02:00
|
|
|
}
|
|
|
|
|
2020-04-28 23:41:18 +02:00
|
|
|
c.up[id] = conn
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
conn.pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
2020-04-24 19:38:21 +02:00
|
|
|
sendICE(c, id, candidate)
|
|
|
|
})
|
|
|
|
|
2020-08-11 15:13:30 +02:00
|
|
|
conn.pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
|
|
|
|
if state == webrtc.ICEConnectionStateFailed {
|
|
|
|
c.action(connectionFailedAction{id: id})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2020-08-06 23:55:00 +02:00
|
|
|
return conn, true, nil
|
2020-04-29 01:57:37 +02:00
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func delUpConn(c *webClient, id string) bool {
|
2020-04-28 14:54:50 +02:00
|
|
|
c.mu.Lock()
|
2020-04-24 19:38:21 +02:00
|
|
|
if c.up == nil {
|
2020-06-08 19:10:08 +02:00
|
|
|
c.mu.Unlock()
|
2020-05-02 23:41:47 +02:00
|
|
|
return false
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
conn := c.up[id]
|
|
|
|
if conn == nil {
|
2020-06-08 19:10:08 +02:00
|
|
|
c.mu.Unlock()
|
2020-05-02 23:41:47 +02:00
|
|
|
return false
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-06-08 19:10:08 +02:00
|
|
|
delete(c.up, id)
|
|
|
|
c.mu.Unlock()
|
2020-04-24 19:38:21 +02:00
|
|
|
|
2020-06-10 20:25:25 +02:00
|
|
|
go func(clients []client) {
|
|
|
|
for _, c := range clients {
|
|
|
|
c.pushConn(conn.id, nil, nil, "")
|
|
|
|
}
|
|
|
|
}(c.Group().getClients(c))
|
|
|
|
|
|
|
|
conn.pc.Close()
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func getDownConn(c *webClient, id string) *rtpDownConnection {
|
2020-04-24 19:38:21 +02:00
|
|
|
if c.down == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-28 14:54:50 +02:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
2020-04-24 19:38:21 +02:00
|
|
|
conn := c.down[id]
|
|
|
|
if conn == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return conn
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func getConn(c *webClient, id string) iceConnection {
|
2020-05-22 22:36:47 +02:00
|
|
|
up := getUpConn(c, id)
|
|
|
|
if up != nil {
|
|
|
|
return up
|
|
|
|
}
|
|
|
|
down := getDownConn(c, id)
|
|
|
|
if down != nil {
|
|
|
|
return down
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
func addDownConn(c *webClient, id string, remote upConnection) (*rtpDownConnection, error) {
|
2020-09-13 13:01:06 +02:00
|
|
|
conn, err := newDownConn(c, id, remote)
|
2020-04-24 19:38:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
2020-04-24 19:38:21 +02:00
|
|
|
|
2020-06-10 20:25:25 +02:00
|
|
|
if c.up != nil && c.up[id] != nil {
|
|
|
|
conn.pc.Close()
|
|
|
|
return nil, errors.New("Adding duplicate connection")
|
|
|
|
}
|
|
|
|
|
2020-04-24 19:38:21 +02:00
|
|
|
if c.down == nil {
|
2020-05-26 17:44:21 +02:00
|
|
|
c.down = make(map[string]*rtpDownConnection)
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-05-23 02:22:43 +02:00
|
|
|
|
2020-06-10 20:25:25 +02:00
|
|
|
old := c.down[id]
|
|
|
|
if old != nil {
|
|
|
|
old.pc.Close()
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-06-08 22:14:28 +02:00
|
|
|
|
|
|
|
conn.pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
|
|
|
sendICE(c, id, candidate)
|
|
|
|
})
|
|
|
|
|
2020-06-11 20:22:28 +02:00
|
|
|
conn.pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
|
|
|
|
if state == webrtc.ICEConnectionStateFailed {
|
|
|
|
c.action(connectionFailedAction{id: id})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2020-05-30 03:36:15 +02:00
|
|
|
err = remote.addLocal(conn)
|
|
|
|
if err != nil {
|
|
|
|
conn.pc.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-23 02:22:43 +02:00
|
|
|
|
2020-05-30 03:36:15 +02:00
|
|
|
c.down[id] = conn
|
2020-04-24 19:38:21 +02:00
|
|
|
return conn, nil
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func delDownConn(c *webClient, id string) bool {
|
2020-04-28 14:54:50 +02:00
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
2020-04-24 19:38:21 +02:00
|
|
|
|
|
|
|
if c.down == nil {
|
2020-05-02 23:41:47 +02:00
|
|
|
return false
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-04-25 02:25:51 +02:00
|
|
|
conn := c.down[id]
|
2020-04-24 19:38:21 +02:00
|
|
|
if conn == nil {
|
2020-05-02 23:41:47 +02:00
|
|
|
return false
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-04-28 14:54:50 +02:00
|
|
|
|
2020-05-23 02:22:43 +02:00
|
|
|
conn.remote.delLocal(conn)
|
2020-04-28 14:54:50 +02:00
|
|
|
for _, track := range conn.tracks {
|
2020-05-30 12:33:30 +02:00
|
|
|
// we only insert the track after we get an answer, so
|
|
|
|
// ignore errors here.
|
|
|
|
track.remote.delLocal(track)
|
2020-04-28 14:54:50 +02:00
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
conn.pc.Close()
|
|
|
|
delete(c.down, id)
|
2020-05-02 23:41:47 +02:00
|
|
|
return true
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
func addDownTrack(c *webClient, conn *rtpDownConnection, remoteTrack upTrack, remoteConn upConnection) (*webrtc.RTPSender, error) {
|
|
|
|
var pt uint8
|
|
|
|
var ssrc uint32
|
|
|
|
var id, label string
|
|
|
|
switch rt := remoteTrack.(type) {
|
|
|
|
case *rtpUpTrack:
|
|
|
|
pt = rt.track.PayloadType()
|
|
|
|
ssrc = rt.track.SSRC()
|
|
|
|
id = rt.track.ID()
|
|
|
|
label = rt.track.Label()
|
|
|
|
default:
|
|
|
|
return nil, errors.New("not implemented yet")
|
|
|
|
}
|
|
|
|
|
|
|
|
local, err := conn.pc.NewTrack(pt, ssrc, id, label)
|
2020-04-24 19:38:21 +02:00
|
|
|
if err != nil {
|
2020-05-17 23:51:17 +02:00
|
|
|
return nil, err
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
|
2020-04-28 14:54:50 +02:00
|
|
|
s, err := conn.pc.AddTrack(local)
|
|
|
|
if err != nil {
|
2020-05-17 23:51:17 +02:00
|
|
|
return nil, err
|
2020-04-28 14:54:50 +02:00
|
|
|
}
|
2020-04-26 01:33:18 +02:00
|
|
|
|
2020-05-26 17:44:21 +02:00
|
|
|
track := &rtpDownTrack{
|
2020-06-12 17:39:16 +02:00
|
|
|
track: local,
|
|
|
|
remote: remoteTrack,
|
|
|
|
maxBitrate: new(bitrate),
|
|
|
|
stats: new(receiverStats),
|
|
|
|
rate: estimator.New(time.Second),
|
2020-04-28 15:26:50 +02:00
|
|
|
}
|
2020-04-28 14:54:50 +02:00
|
|
|
conn.tracks = append(conn.tracks, track)
|
|
|
|
|
2020-05-07 10:29:48 +02:00
|
|
|
go rtcpDownListener(conn, track, s)
|
2020-04-24 19:38:21 +02:00
|
|
|
|
2020-05-17 23:51:17 +02:00
|
|
|
return s, nil
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
|
2020-07-16 20:51:51 +02:00
|
|
|
func negotiate(c *webClient, down *rtpDownConnection, renegotiate, restartIce bool) error {
|
|
|
|
options := webrtc.OfferOptions{ICERestart: restartIce}
|
|
|
|
offer, err := down.pc.CreateOffer(&options)
|
2020-04-24 19:38:21 +02:00
|
|
|
if err != nil {
|
2020-05-17 22:31:29 +02:00
|
|
|
return err
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
|
2020-05-17 22:31:29 +02:00
|
|
|
err = down.pc.SetLocalDescription(offer)
|
2020-04-24 19:38:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-17 22:31:29 +02:00
|
|
|
|
|
|
|
labels := make(map[string]string)
|
2020-05-21 22:30:31 +02:00
|
|
|
for _, t := range down.pc.GetTransceivers() {
|
|
|
|
var track *webrtc.Track
|
|
|
|
if t.Sender() != nil {
|
|
|
|
track = t.Sender().Track()
|
|
|
|
}
|
|
|
|
if track == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tr := range down.tracks {
|
|
|
|
if tr.track == track {
|
2020-06-08 22:14:28 +02:00
|
|
|
labels[t.Mid()] = tr.remote.Label()
|
2020-05-21 22:30:31 +02:00
|
|
|
}
|
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-05-17 22:31:29 +02:00
|
|
|
|
2020-08-12 13:56:35 +02:00
|
|
|
kind := ""
|
|
|
|
if renegotiate {
|
|
|
|
kind = "renegotiate"
|
|
|
|
}
|
|
|
|
|
2020-04-24 19:38:21 +02:00
|
|
|
return c.write(clientMessage{
|
2020-08-12 13:56:35 +02:00
|
|
|
Type: "offer",
|
|
|
|
Kind: kind,
|
|
|
|
Id: down.id,
|
|
|
|
Offer: &offer,
|
|
|
|
Labels: labels,
|
2020-04-24 19:38:21 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func sendICE(c *webClient, id string, candidate *webrtc.ICECandidate) error {
|
2020-04-24 19:38:21 +02:00
|
|
|
if candidate == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
cand := candidate.ToJSON()
|
|
|
|
return c.write(clientMessage{
|
|
|
|
Type: "ice",
|
|
|
|
Id: id,
|
|
|
|
Candidate: &cand,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-08-06 23:55:00 +02:00
|
|
|
func gotOffer(c *webClient, id string, offer webrtc.SessionDescription, renegotiate bool, labels map[string]string) error {
|
|
|
|
if !renegotiate {
|
|
|
|
// unless the client indicates that this is a compatible
|
|
|
|
// renegotiation, tear down the existing connection.
|
|
|
|
delUpConn(c, id)
|
|
|
|
}
|
|
|
|
|
2020-09-03 13:34:39 +02:00
|
|
|
up, isnew, err := addUpConn(c, id)
|
2020-06-10 20:25:25 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-08-06 23:55:00 +02:00
|
|
|
|
2020-08-12 11:28:49 +02:00
|
|
|
if u := c.Credentials().Username; u != "" {
|
|
|
|
up.label = u
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
err = up.pc.SetRemoteDescription(offer)
|
|
|
|
if err != nil {
|
2020-09-03 13:34:39 +02:00
|
|
|
if renegotiate && !isnew {
|
|
|
|
// create a new PC from scratch
|
|
|
|
log.Printf("SetRemoteDescription(offer): %v", err)
|
|
|
|
return gotOffer(c, id, offer, false, labels)
|
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
answer, err := up.pc.CreateAnswer(nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = up.pc.SetLocalDescription(answer)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-05-17 22:31:29 +02:00
|
|
|
up.labels = labels
|
|
|
|
|
2020-05-22 23:07:38 +02:00
|
|
|
err = up.flushICECandidates()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("ICE: %v", err)
|
|
|
|
}
|
|
|
|
|
2020-04-24 19:38:21 +02:00
|
|
|
return c.write(clientMessage{
|
|
|
|
Type: "answer",
|
|
|
|
Id: id,
|
|
|
|
Answer: &answer,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func gotAnswer(c *webClient, id string, answer webrtc.SessionDescription) error {
|
2020-05-22 23:07:38 +02:00
|
|
|
down := getDownConn(c, id)
|
|
|
|
if down == nil {
|
2020-04-24 19:38:21 +02:00
|
|
|
return protocolError("unknown id in answer")
|
|
|
|
}
|
2020-05-22 23:07:38 +02:00
|
|
|
err := down.pc.SetRemoteDescription(answer)
|
2020-04-24 19:38:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-21 00:55:00 +02:00
|
|
|
|
2020-05-22 23:07:38 +02:00
|
|
|
err = down.flushICECandidates()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("ICE: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, t := range down.tracks {
|
2020-05-26 17:01:29 +02:00
|
|
|
t.remote.addLocal(t)
|
2020-05-21 00:55:00 +02:00
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func gotICE(c *webClient, candidate *webrtc.ICECandidateInit, id string) error {
|
2020-05-22 22:36:47 +02:00
|
|
|
conn := getConn(c, id)
|
|
|
|
if conn == nil {
|
|
|
|
return errors.New("unknown id in ICE")
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-05-22 23:07:38 +02:00
|
|
|
return conn.addICECandidate(candidate)
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func (c *webClient) setRequested(requested map[string]uint32) error {
|
2020-05-09 19:39:34 +02:00
|
|
|
if c.down != nil {
|
|
|
|
for id := range c.down {
|
|
|
|
c.write(clientMessage{
|
|
|
|
Type: "close",
|
|
|
|
Id: id,
|
|
|
|
})
|
|
|
|
delDownConn(c, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-17 22:31:29 +02:00
|
|
|
c.requested = requested
|
2020-05-09 19:39:34 +02:00
|
|
|
|
2020-05-30 00:23:54 +02:00
|
|
|
go pushConns(c)
|
2020-05-09 19:39:34 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-30 00:23:54 +02:00
|
|
|
func pushConns(c client) {
|
2020-06-08 22:14:28 +02:00
|
|
|
clients := c.Group().getClients(c)
|
2020-05-30 00:23:54 +02:00
|
|
|
for _, cc := range clients {
|
|
|
|
ccc, ok := cc.(*webClient)
|
|
|
|
if ok {
|
|
|
|
ccc.action(pushConnsAction{c})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func (c *webClient) isRequested(label string) bool {
|
2020-05-18 00:35:58 +02:00
|
|
|
return c.requested[label] != 0
|
2020-05-09 19:39:34 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
func addDownConnTracks(c *webClient, remote upConnection, tracks []upTrack) (*rtpDownConnection, error) {
|
2020-05-17 23:51:17 +02:00
|
|
|
requested := false
|
|
|
|
for _, t := range tracks {
|
2020-06-08 22:14:28 +02:00
|
|
|
if c.isRequested(t.Label()) {
|
2020-05-17 23:51:17 +02:00
|
|
|
requested = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !requested {
|
|
|
|
return nil, nil
|
2020-05-12 18:27:40 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
down, err := addDownConn(c, remote.Id(), remote)
|
2020-05-17 23:51:17 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-12 18:27:40 +02:00
|
|
|
|
2020-05-17 23:51:17 +02:00
|
|
|
for _, t := range tracks {
|
2020-06-08 22:14:28 +02:00
|
|
|
if !c.isRequested(t.Label()) {
|
2020-05-17 23:51:17 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
_, err = addDownTrack(c, down, t, remote)
|
|
|
|
if err != nil {
|
|
|
|
delDownConn(c, down.id)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-31 23:35:56 +02:00
|
|
|
go rtcpDownSender(down)
|
|
|
|
|
2020-05-17 23:51:17 +02:00
|
|
|
return down, nil
|
|
|
|
}
|
|
|
|
|
2020-06-10 19:43:08 +02:00
|
|
|
func (c *webClient) pushConn(id string, conn upConnection, tracks []upTrack, label string) error {
|
|
|
|
err := c.action(pushConnAction{id, conn, tracks})
|
2020-05-28 02:35:09 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-06-10 19:43:08 +02:00
|
|
|
if conn != nil && label != "" {
|
2020-06-08 22:14:28 +02:00
|
|
|
err := c.action(addLabelAction{conn.Id(), conn.Label()})
|
2020-05-28 02:35:09 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-12 18:27:40 +02:00
|
|
|
}
|
2020-05-28 02:35:09 +02:00
|
|
|
return nil
|
2020-05-12 18:27:40 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 22:14:28 +02:00
|
|
|
func startClient(conn *websocket.Conn) (err error) {
|
|
|
|
var m clientMessage
|
|
|
|
|
|
|
|
err = conn.SetReadDeadline(time.Now().Add(15 * time.Second))
|
|
|
|
if err != nil {
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = conn.ReadJSON(&m)
|
|
|
|
if err != nil {
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = conn.SetReadDeadline(time.Time{})
|
|
|
|
if err != nil {
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-08-12 11:50:30 +02:00
|
|
|
if m.Type != "login" {
|
|
|
|
conn.WriteMessage(websocket.CloseMessage,
|
|
|
|
websocket.FormatCloseMessage(
|
|
|
|
websocket.CloseProtocolError,
|
|
|
|
"you must login first",
|
|
|
|
),
|
|
|
|
)
|
2020-06-08 22:14:28 +02:00
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c := &webClient{
|
2020-08-12 11:28:49 +02:00
|
|
|
id: m.Id,
|
|
|
|
credentials: clientCredentials{
|
|
|
|
m.Username,
|
|
|
|
m.Password,
|
|
|
|
},
|
2020-06-08 22:14:28 +02:00
|
|
|
actionCh: make(chan interface{}, 10),
|
|
|
|
done: make(chan struct{}),
|
|
|
|
}
|
|
|
|
|
|
|
|
defer close(c.done)
|
|
|
|
|
|
|
|
c.writeCh = make(chan interface{}, 25)
|
|
|
|
defer func() {
|
|
|
|
if isWSNormalError(err) {
|
|
|
|
err = nil
|
2020-09-08 00:29:30 +02:00
|
|
|
c.close(nil)
|
2020-06-08 22:14:28 +02:00
|
|
|
} else {
|
|
|
|
m, e := errorToWSCloseMessage(err)
|
|
|
|
if m != "" {
|
|
|
|
c.write(clientMessage{
|
2020-08-12 12:17:56 +02:00
|
|
|
Type: "usermessage",
|
|
|
|
Kind: "error",
|
2020-06-08 22:14:28 +02:00
|
|
|
Value: m,
|
|
|
|
})
|
|
|
|
}
|
2020-09-08 00:29:30 +02:00
|
|
|
c.close(e)
|
2020-06-08 22:14:28 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
c.writerDone = make(chan struct{})
|
|
|
|
go clientWriter(conn, c.writeCh, c.writerDone)
|
|
|
|
|
2020-08-12 11:50:30 +02:00
|
|
|
err = conn.ReadJSON(&m)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.Type != "join" {
|
|
|
|
return protocolError("you must join a group first")
|
|
|
|
}
|
|
|
|
|
2020-08-12 11:28:49 +02:00
|
|
|
g, err := addClient(m.Group, c)
|
2020-06-08 22:14:28 +02:00
|
|
|
if err != nil {
|
2020-09-10 13:39:38 +02:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
err = userError("group does not exist")
|
|
|
|
}
|
2020-06-08 22:14:28 +02:00
|
|
|
return
|
|
|
|
}
|
2020-09-13 13:24:05 +02:00
|
|
|
if redirect := g.Redirect(); redirect != "" {
|
2020-09-10 13:55:57 +02:00
|
|
|
// We normally redirect at the HTTP level, but the group
|
|
|
|
// description could have been edited in the meantime.
|
2020-09-13 13:24:05 +02:00
|
|
|
err = userError("group is now at " + redirect)
|
2020-09-10 13:55:57 +02:00
|
|
|
return
|
|
|
|
}
|
2020-06-08 22:14:28 +02:00
|
|
|
c.group = g
|
|
|
|
defer delClient(c)
|
|
|
|
|
|
|
|
return clientLoop(c, conn)
|
|
|
|
}
|
|
|
|
|
2020-09-13 11:57:33 +02:00
|
|
|
type pushConnAction struct {
|
|
|
|
id string
|
|
|
|
conn upConnection
|
|
|
|
tracks []upTrack
|
|
|
|
}
|
|
|
|
|
|
|
|
type addLabelAction struct {
|
|
|
|
id string
|
|
|
|
label string
|
|
|
|
}
|
|
|
|
|
|
|
|
type pushConnsAction struct {
|
|
|
|
c client
|
|
|
|
}
|
|
|
|
|
|
|
|
type connectionFailedAction struct {
|
|
|
|
id string
|
|
|
|
}
|
|
|
|
|
|
|
|
type permissionsChangedAction struct{}
|
|
|
|
|
|
|
|
type kickAction struct {
|
|
|
|
message string
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func clientLoop(c *webClient, conn *websocket.Conn) error {
|
2020-04-24 19:38:21 +02:00
|
|
|
read := make(chan interface{}, 1)
|
|
|
|
go clientReader(conn, read, c.done)
|
|
|
|
|
|
|
|
defer func() {
|
2020-05-18 00:35:58 +02:00
|
|
|
c.setRequested(map[string]uint32{})
|
2020-04-24 19:38:21 +02:00
|
|
|
if c.up != nil {
|
|
|
|
for id := range c.up {
|
|
|
|
delUpConn(c, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-04-25 02:25:51 +02:00
|
|
|
c.write(clientMessage{
|
|
|
|
Type: "permissions",
|
|
|
|
Permissions: c.permissions,
|
|
|
|
})
|
|
|
|
|
2020-04-25 21:16:49 +02:00
|
|
|
h := c.group.getChatHistory()
|
|
|
|
for _, m := range h {
|
|
|
|
err := c.write(clientMessage{
|
|
|
|
Type: "chat",
|
|
|
|
Id: m.id,
|
|
|
|
Username: m.user,
|
|
|
|
Value: m.value,
|
2020-08-12 12:17:56 +02:00
|
|
|
Kind: m.kind,
|
2020-04-25 21:16:49 +02:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-25 22:44:24 +02:00
|
|
|
readTime := time.Now()
|
|
|
|
|
2020-06-12 17:39:16 +02:00
|
|
|
ticker := time.NewTicker(10 * time.Second)
|
2020-04-24 19:38:21 +02:00
|
|
|
defer ticker.Stop()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case m, ok := <-read:
|
|
|
|
if !ok {
|
|
|
|
return errors.New("reader died")
|
|
|
|
}
|
|
|
|
switch m := m.(type) {
|
|
|
|
case clientMessage:
|
2020-04-25 22:44:24 +02:00
|
|
|
readTime = time.Now()
|
2020-04-24 19:38:21 +02:00
|
|
|
err := handleClientMessage(c, m)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
case error:
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
case a := <-c.actionCh:
|
|
|
|
switch a := a.(type) {
|
2020-06-10 19:43:08 +02:00
|
|
|
case pushConnAction:
|
|
|
|
if a.conn == nil {
|
2020-06-10 20:25:25 +02:00
|
|
|
found := delDownConn(c, a.id)
|
2020-06-10 19:43:08 +02:00
|
|
|
if found {
|
|
|
|
c.write(clientMessage{
|
|
|
|
Type: "close",
|
|
|
|
Id: a.id,
|
|
|
|
})
|
2020-06-10 20:25:25 +02:00
|
|
|
} else {
|
|
|
|
log.Printf("Deleting unknown " +
|
|
|
|
"down connection")
|
2020-06-10 19:43:08 +02:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2020-05-17 23:51:17 +02:00
|
|
|
down, err := addDownConnTracks(
|
|
|
|
c, a.conn, a.tracks,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-05-17 23:51:17 +02:00
|
|
|
if down != nil {
|
2020-07-16 20:51:51 +02:00
|
|
|
err = negotiate(c, down, false, false)
|
2020-04-24 19:38:21 +02:00
|
|
|
if err != nil {
|
2020-05-24 13:36:42 +02:00
|
|
|
log.Printf("Negotiate: %v", err)
|
|
|
|
delDownConn(c, down.id)
|
|
|
|
err = failConnection(
|
|
|
|
c, down.id,
|
|
|
|
"negotiation failed",
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
continue
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
case addLabelAction:
|
|
|
|
c.write(clientMessage{
|
|
|
|
Type: "label",
|
|
|
|
Id: a.id,
|
|
|
|
Value: a.label,
|
|
|
|
})
|
2020-05-17 23:51:17 +02:00
|
|
|
case pushConnsAction:
|
2020-04-24 19:38:21 +02:00
|
|
|
for _, u := range c.up {
|
2020-06-08 19:10:08 +02:00
|
|
|
tracks := u.getTracks()
|
2020-06-08 22:14:28 +02:00
|
|
|
ts := make([]upTrack, len(tracks))
|
|
|
|
for i, t := range tracks {
|
|
|
|
ts[i] = t
|
|
|
|
}
|
2020-06-10 19:43:08 +02:00
|
|
|
go a.c.pushConn(u.id, u, ts, u.label)
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-05-02 23:41:47 +02:00
|
|
|
case connectionFailedAction:
|
2020-08-11 15:13:30 +02:00
|
|
|
if down := getDownConn(c, a.id); down != nil {
|
|
|
|
err := negotiate(c, down, true, true)
|
2020-08-13 13:48:17 +02:00
|
|
|
if err != nil {
|
2020-08-11 15:13:30 +02:00
|
|
|
return err
|
|
|
|
}
|
2020-07-16 20:51:51 +02:00
|
|
|
tracks := make(
|
|
|
|
[]upTrack, len(down.tracks),
|
|
|
|
)
|
|
|
|
for i, t := range down.tracks {
|
|
|
|
tracks[i] = t.remote
|
|
|
|
}
|
|
|
|
go c.pushConn(
|
|
|
|
down.remote.Id(), down.remote,
|
|
|
|
tracks, down.remote.Label(),
|
|
|
|
)
|
2020-08-11 15:13:30 +02:00
|
|
|
} else if up := getUpConn(c, a.id); up != nil {
|
|
|
|
c.write(clientMessage{
|
|
|
|
Type: "renegotiate",
|
|
|
|
Id: a.id,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
log.Printf("Attempting to renegotiate " +
|
|
|
|
"unknown connection")
|
2020-06-11 20:22:28 +02:00
|
|
|
}
|
2020-08-11 15:13:30 +02:00
|
|
|
|
2020-04-25 18:35:32 +02:00
|
|
|
case permissionsChangedAction:
|
2020-04-25 17:36:35 +02:00
|
|
|
c.write(clientMessage{
|
|
|
|
Type: "permissions",
|
|
|
|
Permissions: c.permissions,
|
|
|
|
})
|
2020-04-25 19:58:54 +02:00
|
|
|
if !c.permissions.Present {
|
2020-06-08 19:10:08 +02:00
|
|
|
up := getUpConns(c)
|
|
|
|
for _, u := range up {
|
|
|
|
found := delUpConn(c, u.id)
|
2020-05-24 13:36:42 +02:00
|
|
|
if found {
|
|
|
|
failConnection(
|
2020-06-08 19:10:08 +02:00
|
|
|
c, u.id,
|
2020-05-24 13:36:42 +02:00
|
|
|
"permission denied",
|
|
|
|
)
|
|
|
|
}
|
2020-04-25 18:35:32 +02:00
|
|
|
}
|
|
|
|
}
|
2020-04-25 17:36:35 +02:00
|
|
|
case kickAction:
|
2020-09-12 12:26:07 +02:00
|
|
|
return userError(a.message)
|
2020-04-24 19:38:21 +02:00
|
|
|
default:
|
|
|
|
log.Printf("unexpected action %T", a)
|
|
|
|
return errors.New("unexpected action")
|
|
|
|
}
|
|
|
|
case <-ticker.C:
|
2020-04-25 22:44:24 +02:00
|
|
|
if time.Since(readTime) > 90*time.Second {
|
|
|
|
return errors.New("client is dead")
|
|
|
|
}
|
|
|
|
if time.Since(readTime) > 60*time.Second {
|
|
|
|
err := c.write(clientMessage{
|
|
|
|
Type: "ping",
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func failConnection(c *webClient, id string, message string) error {
|
2020-05-24 13:36:42 +02:00
|
|
|
if id != "" {
|
|
|
|
err := c.write(clientMessage{
|
|
|
|
Type: "abort",
|
|
|
|
Id: id,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if message != "" {
|
|
|
|
err := c.error(userError(message))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-28 02:35:09 +02:00
|
|
|
func setPermissions(g *group, id string, perm string) error {
|
2020-09-13 13:24:05 +02:00
|
|
|
client := g.getClient(id)
|
2020-05-28 02:35:09 +02:00
|
|
|
if client == nil {
|
|
|
|
return userError("no such user")
|
|
|
|
}
|
|
|
|
|
|
|
|
c, ok := client.(*webClient)
|
|
|
|
if !ok {
|
|
|
|
return userError("this is not a real user")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch perm {
|
|
|
|
case "op":
|
|
|
|
c.permissions.Op = true
|
2020-09-13 13:24:05 +02:00
|
|
|
if g.AllowRecording() {
|
2020-05-30 00:23:54 +02:00
|
|
|
c.permissions.Record = true
|
|
|
|
}
|
2020-05-28 02:35:09 +02:00
|
|
|
case "unop":
|
|
|
|
c.permissions.Op = false
|
2020-05-30 00:23:54 +02:00
|
|
|
c.permissions.Record = false
|
2020-05-28 02:35:09 +02:00
|
|
|
case "present":
|
|
|
|
c.permissions.Present = true
|
|
|
|
case "unpresent":
|
|
|
|
c.permissions.Present = false
|
|
|
|
default:
|
|
|
|
return userError("unknown permission")
|
|
|
|
}
|
|
|
|
return c.action(permissionsChangedAction{})
|
|
|
|
}
|
|
|
|
|
2020-09-12 12:42:48 +02:00
|
|
|
func (c *webClient) kick(message string) error {
|
|
|
|
return c.action(kickAction{message})
|
|
|
|
}
|
|
|
|
|
2020-09-12 12:26:07 +02:00
|
|
|
func kickClient(g *group, id string, message string) error {
|
2020-09-12 14:00:14 +02:00
|
|
|
client := g.getClient(id)
|
2020-05-28 02:35:09 +02:00
|
|
|
if client == nil {
|
|
|
|
return userError("no such user")
|
|
|
|
}
|
|
|
|
|
2020-09-12 12:42:48 +02:00
|
|
|
c, ok := client.(kickable)
|
2020-05-28 02:35:09 +02:00
|
|
|
if !ok {
|
2020-09-12 12:42:48 +02:00
|
|
|
return userError("this client is not kickable")
|
2020-05-28 02:35:09 +02:00
|
|
|
}
|
|
|
|
|
2020-09-12 12:42:48 +02:00
|
|
|
return c.kick(message)
|
2020-05-28 02:35:09 +02:00
|
|
|
}
|
|
|
|
|
2020-05-27 11:44:49 +02:00
|
|
|
func handleClientMessage(c *webClient, m clientMessage) error {
|
2020-04-24 19:38:21 +02:00
|
|
|
switch m.Type {
|
2020-05-09 19:39:34 +02:00
|
|
|
case "request":
|
2020-05-17 22:31:29 +02:00
|
|
|
err := c.setRequested(m.Request)
|
2020-05-09 19:39:34 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
case "offer":
|
2020-04-25 02:25:51 +02:00
|
|
|
if !c.permissions.Present {
|
2020-04-25 18:29:44 +02:00
|
|
|
c.write(clientMessage{
|
|
|
|
Type: "abort",
|
|
|
|
Id: m.Id,
|
|
|
|
})
|
|
|
|
return c.error(userError("not authorised"))
|
2020-04-25 02:25:51 +02:00
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
if m.Offer == nil {
|
|
|
|
return protocolError("null offer")
|
|
|
|
}
|
2020-08-12 13:56:35 +02:00
|
|
|
err := gotOffer(
|
|
|
|
c, m.Id, *m.Offer, m.Kind == "renegotiate", m.Labels,
|
|
|
|
)
|
2020-04-24 19:38:21 +02:00
|
|
|
if err != nil {
|
2020-05-24 13:36:42 +02:00
|
|
|
log.Printf("gotOffer: %v", err)
|
|
|
|
return failConnection(c, m.Id, "negotiation failed")
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
|
|
|
case "answer":
|
|
|
|
if m.Answer == nil {
|
|
|
|
return protocolError("null answer")
|
|
|
|
}
|
2020-05-17 22:31:29 +02:00
|
|
|
err := gotAnswer(c, m.Id, *m.Answer)
|
2020-04-24 19:38:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-08-11 15:13:30 +02:00
|
|
|
case "renegotiate":
|
|
|
|
down := getDownConn(c, m.Id)
|
|
|
|
if down != nil {
|
|
|
|
err := negotiate(c, down, true, true)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Printf("Trying to renegotiate unknown connection")
|
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
case "close":
|
2020-05-02 23:41:47 +02:00
|
|
|
found := delUpConn(c, m.Id)
|
|
|
|
if !found {
|
|
|
|
log.Printf("Deleting unknown up connection %v", m.Id)
|
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
case "ice":
|
|
|
|
if m.Candidate == nil {
|
|
|
|
return protocolError("null candidate")
|
|
|
|
}
|
|
|
|
err := gotICE(c, m.Candidate, m.Id)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("ICE: %v", err)
|
|
|
|
}
|
|
|
|
case "chat":
|
2020-08-12 12:17:56 +02:00
|
|
|
c.group.addToChatHistory(m.Id, m.Username, m.Kind, m.Value)
|
2020-08-07 11:33:59 +02:00
|
|
|
clients := c.group.getClients(nil)
|
2020-04-24 19:38:21 +02:00
|
|
|
for _, cc := range clients {
|
2020-05-28 02:35:09 +02:00
|
|
|
cc, ok := cc.(*webClient)
|
|
|
|
if ok {
|
|
|
|
cc.write(m)
|
|
|
|
}
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-08-12 12:40:40 +02:00
|
|
|
case "groupaction":
|
|
|
|
switch m.Kind {
|
|
|
|
case "clearchat":
|
|
|
|
c.group.clearChatHistory()
|
|
|
|
m := clientMessage{Type: "clearchat"}
|
|
|
|
clients := c.group.getClients(nil)
|
|
|
|
for _, cc := range clients {
|
|
|
|
cc, ok := cc.(*webClient)
|
|
|
|
if ok {
|
|
|
|
cc.write(m)
|
|
|
|
}
|
2020-05-28 02:35:09 +02:00
|
|
|
}
|
2020-08-12 12:40:40 +02:00
|
|
|
case "lock", "unlock":
|
|
|
|
if !c.permissions.Op {
|
|
|
|
return c.error(userError("not authorised"))
|
2020-05-30 00:23:54 +02:00
|
|
|
}
|
2020-09-13 13:24:05 +02:00
|
|
|
c.group.SetLocked(m.Kind == "lock")
|
2020-08-12 12:40:40 +02:00
|
|
|
case "record":
|
|
|
|
if !c.permissions.Record {
|
|
|
|
return c.error(userError("not authorised"))
|
|
|
|
}
|
|
|
|
for _, cc := range c.group.getClients(c) {
|
|
|
|
_, ok := cc.(*diskClient)
|
|
|
|
if ok {
|
|
|
|
return c.error(userError("already recording"))
|
|
|
|
}
|
|
|
|
}
|
2020-09-13 14:14:13 +02:00
|
|
|
disk := NewDiskClient(c.group)
|
2020-08-12 12:40:40 +02:00
|
|
|
_, err := addClient(c.group.name, disk)
|
|
|
|
if err != nil {
|
2020-05-30 00:23:54 +02:00
|
|
|
disk.Close()
|
2020-08-12 12:40:40 +02:00
|
|
|
return c.error(err)
|
2020-05-30 00:23:54 +02:00
|
|
|
}
|
2020-08-12 12:40:40 +02:00
|
|
|
go pushConns(disk)
|
|
|
|
case "unrecord":
|
|
|
|
if !c.permissions.Record {
|
|
|
|
return c.error(userError("not authorised"))
|
|
|
|
}
|
|
|
|
for _, cc := range c.group.getClients(c) {
|
|
|
|
disk, ok := cc.(*diskClient)
|
|
|
|
if ok {
|
|
|
|
disk.Close()
|
|
|
|
delClient(disk)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return protocolError("unknown group action")
|
2020-05-30 00:23:54 +02:00
|
|
|
}
|
2020-08-12 12:40:40 +02:00
|
|
|
case "useraction":
|
|
|
|
switch m.Kind {
|
|
|
|
case "op", "unop", "present", "unpresent":
|
|
|
|
if !c.permissions.Op {
|
|
|
|
return c.error(userError("not authorised"))
|
|
|
|
}
|
|
|
|
err := setPermissions(c.group, m.Id, m.Kind)
|
|
|
|
if err != nil {
|
|
|
|
return c.error(err)
|
|
|
|
}
|
|
|
|
case "kick":
|
|
|
|
if !c.permissions.Op {
|
|
|
|
return c.error(userError("not authorised"))
|
|
|
|
}
|
2020-09-12 12:26:07 +02:00
|
|
|
message := m.Value
|
|
|
|
if message == "" {
|
|
|
|
message = "you have been kicked"
|
|
|
|
}
|
|
|
|
err := kickClient(c.group, m.Id, message)
|
2020-08-12 12:40:40 +02:00
|
|
|
if err != nil {
|
|
|
|
return c.error(err)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return protocolError("unknown user action")
|
2020-04-25 17:36:35 +02:00
|
|
|
}
|
2020-04-25 22:44:24 +02:00
|
|
|
case "pong":
|
|
|
|
// nothing
|
|
|
|
case "ping":
|
|
|
|
c.write(clientMessage{
|
|
|
|
Type: "pong",
|
|
|
|
})
|
2020-04-24 19:38:21 +02:00
|
|
|
default:
|
|
|
|
log.Printf("unexpected message: %v", m.Type)
|
|
|
|
return protocolError("unexpected message")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func clientReader(conn *websocket.Conn, read chan<- interface{}, done <-chan struct{}) {
|
|
|
|
defer close(read)
|
|
|
|
for {
|
|
|
|
var m clientMessage
|
|
|
|
err := conn.ReadJSON(&m)
|
|
|
|
if err != nil {
|
|
|
|
select {
|
|
|
|
case read <- err:
|
|
|
|
return
|
|
|
|
case <-done:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case read <- m:
|
|
|
|
case <-done:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func clientWriter(conn *websocket.Conn, ch <-chan interface{}, done chan<- struct{}) {
|
|
|
|
defer func() {
|
|
|
|
close(done)
|
|
|
|
conn.Close()
|
|
|
|
}()
|
|
|
|
|
|
|
|
for {
|
|
|
|
m, ok := <-ch
|
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
err := conn.SetWriteDeadline(
|
|
|
|
time.Now().Add(2 * time.Second))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
switch m := m.(type) {
|
|
|
|
case clientMessage:
|
|
|
|
err := conn.WriteJSON(m)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case closeMessage:
|
2020-09-08 00:29:30 +02:00
|
|
|
if m.data != nil {
|
|
|
|
conn.WriteMessage(
|
|
|
|
websocket.CloseMessage,
|
|
|
|
m.data,
|
|
|
|
)
|
2020-04-24 19:38:21 +02:00
|
|
|
}
|
2020-09-08 00:29:30 +02:00
|
|
|
return
|
2020-04-24 19:38:21 +02:00
|
|
|
default:
|
|
|
|
log.Printf("clientWiter: unexpected message %T", m)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-28 02:35:09 +02:00
|
|
|
|
2020-09-12 19:52:36 +02:00
|
|
|
var ErrClientDead = errors.New("client is dead")
|
2020-05-30 12:38:13 +02:00
|
|
|
|
2020-05-28 02:35:09 +02:00
|
|
|
func (c *webClient) action(m interface{}) error {
|
|
|
|
select {
|
|
|
|
case c.actionCh <- m:
|
|
|
|
return nil
|
|
|
|
case <-c.done:
|
2020-05-30 12:38:13 +02:00
|
|
|
return ErrClientDead
|
2020-05-28 02:35:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *webClient) write(m clientMessage) error {
|
|
|
|
select {
|
|
|
|
case c.writeCh <- m:
|
|
|
|
return nil
|
|
|
|
case <-c.writerDone:
|
2020-09-12 19:52:36 +02:00
|
|
|
return ErrClientDead
|
2020-05-28 02:35:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-08 00:29:30 +02:00
|
|
|
func (c *webClient) close(data []byte) error {
|
|
|
|
select {
|
|
|
|
case c.writeCh <- closeMessage{data}:
|
|
|
|
return nil
|
|
|
|
case <-c.writerDone:
|
2020-09-12 19:52:36 +02:00
|
|
|
return ErrClientDead
|
2020-09-08 00:29:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-28 02:35:09 +02:00
|
|
|
func (c *webClient) error(err error) error {
|
|
|
|
switch e := err.(type) {
|
|
|
|
case userError:
|
|
|
|
return c.write(clientMessage{
|
2020-08-12 12:17:56 +02:00
|
|
|
Type: "usermessage",
|
|
|
|
Kind: "error",
|
2020-05-28 02:35:09 +02:00
|
|
|
Value: string(e),
|
|
|
|
})
|
|
|
|
default:
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|