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.
318 lines
8.7 KiB
318 lines
8.7 KiB
/* |
|
* Spreed WebRTC. |
|
* Copyright (C) 2013-2014 struktur AG |
|
* |
|
* This file is part of Spreed WebRTC. |
|
* |
|
* 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 ( |
|
"log" |
|
"time" |
|
) |
|
|
|
const ( |
|
maxConferenceSize = 100 |
|
) |
|
|
|
type ChannellingAPI interface { |
|
OnConnect(Client, *Session) (interface{}, error) |
|
OnDisconnect(Client, *Session) |
|
OnIncoming(Sender, *Session, *DataIncoming) (interface{}, error) |
|
} |
|
|
|
type channellingAPI struct { |
|
*Config |
|
RoomStatusManager |
|
SessionEncoder |
|
SessionManager |
|
StatsCounter |
|
ContactManager |
|
TurnDataCreator |
|
Unicaster |
|
} |
|
|
|
func NewChannellingAPI(config *Config, roomStatus RoomStatusManager, sessionEncoder SessionEncoder, sessionManager SessionManager, statsCounter StatsCounter, contactManager ContactManager, turnDataCreator TurnDataCreator, unicaster Unicaster) ChannellingAPI { |
|
return &channellingAPI{ |
|
config, |
|
roomStatus, |
|
sessionEncoder, |
|
sessionManager, |
|
statsCounter, |
|
contactManager, |
|
turnDataCreator, |
|
unicaster, |
|
} |
|
} |
|
|
|
func (api *channellingAPI) OnConnect(client Client, session *Session) (interface{}, error) { |
|
api.Unicaster.OnConnect(client, session) |
|
return api.MakeSelf(session) |
|
} |
|
|
|
func (api *channellingAPI) OnDisconnect(client Client, session *Session) { |
|
api.Unicaster.OnDisconnect(client, session) |
|
} |
|
|
|
func (api *channellingAPI) OnIncoming(sender Sender, session *Session, msg *DataIncoming) (interface{}, error) { |
|
switch msg.Type { |
|
case "Self": |
|
return api.MakeSelf(session) |
|
case "Hello": |
|
if msg.Hello == nil { |
|
return nil, NewDataError("bad_request", "message did not contain Hello") |
|
} |
|
|
|
return api.HandleHello(session, msg.Hello, sender) |
|
case "Offer": |
|
if msg.Offer == nil { |
|
log.Println("Received invalid offer message.", msg) |
|
break |
|
} |
|
|
|
// TODO(longsleep): Validate offer |
|
session.Unicast(msg.Offer.To, msg.Offer) |
|
case "Candidate": |
|
if msg.Candidate == nil { |
|
log.Println("Received invalid candidate message.", msg) |
|
break |
|
} |
|
|
|
// TODO(longsleep): Validate candidate |
|
session.Unicast(msg.Candidate.To, msg.Candidate) |
|
case "Answer": |
|
if msg.Answer == nil { |
|
log.Println("Received invalid answer message.", msg) |
|
break |
|
} |
|
|
|
// TODO(longsleep): Validate Answer |
|
session.Unicast(msg.Answer.To, msg.Answer) |
|
case "Users": |
|
return api.HandleUsers(session) |
|
case "Authentication": |
|
if msg.Authentication == nil || msg.Authentication.Authentication == nil { |
|
return nil, NewDataError("bad_request", "message did not contain Authentication") |
|
} |
|
|
|
return api.HandleAuthentication(session, msg.Authentication.Authentication) |
|
case "Bye": |
|
if msg.Bye == nil { |
|
log.Println("Received invalid bye message.", msg) |
|
break |
|
} |
|
|
|
session.Unicast(msg.Bye.To, msg.Bye) |
|
case "Status": |
|
if msg.Status == nil { |
|
log.Println("Received invalid status message.", msg) |
|
break |
|
} |
|
|
|
//log.Println("Status", msg.Status) |
|
session.Update(&SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status}) |
|
session.BroadcastStatus() |
|
case "Chat": |
|
if msg.Chat == nil || msg.Chat.Chat == nil { |
|
log.Println("Received invalid chat message.", msg) |
|
break |
|
} |
|
|
|
api.HandleChat(session, msg.Chat.To, msg.Chat.Chat) |
|
case "Conference": |
|
if msg.Conference == nil { |
|
log.Println("Received invalid conference message.", msg) |
|
break |
|
} |
|
|
|
api.HandleConference(session, msg.Conference) |
|
case "Alive": |
|
return msg.Alive, nil |
|
case "Sessions": |
|
if msg.Sessions == nil || msg.Sessions.Sessions == nil { |
|
return nil, NewDataError("bad_request", "message did not contain Sessions") |
|
} |
|
|
|
return api.HandleSessions(session, msg.Sessions.Sessions) |
|
case "Room": |
|
if msg.Room == nil { |
|
return nil, NewDataError("bad_request", "message did not contain Room") |
|
} |
|
|
|
return api.HandleRoom(session, msg.Room) |
|
default: |
|
log.Println("OnText unhandled message type", msg.Type) |
|
} |
|
|
|
return nil, nil |
|
} |
|
|
|
func (api *channellingAPI) MakeSelf(session *Session) (*DataSelf, error) { |
|
token, err := api.EncodeSessionToken(session) |
|
if err != nil { |
|
log.Println("Error in OnRegister", err) |
|
return nil, err |
|
} |
|
|
|
log.Println("Created new session token", len(token), token) |
|
self := &DataSelf{ |
|
Type: "Self", |
|
Id: session.Id, |
|
Sid: session.Sid, |
|
Userid: session.Userid(), |
|
Suserid: api.EncodeSessionUserID(session), |
|
Token: token, |
|
Version: api.Version, |
|
Turn: api.CreateTurnData(session), |
|
Stun: api.StunURIs, |
|
} |
|
|
|
return self, nil |
|
} |
|
|
|
func (api *channellingAPI) HandleHello(session *Session, hello *DataHello, sender Sender) (*DataWelcome, error) { |
|
// TODO(longsleep): Filter room id and user agent. |
|
session.Update(&SessionUpdate{Types: []string{"Ua"}, Ua: hello.Ua}) |
|
|
|
room, err := session.JoinRoom(hello.Id, hello.Credentials, sender) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return &DataWelcome{ |
|
Type: "Welcome", |
|
Room: room, |
|
Users: api.RoomUsers(session), |
|
}, nil |
|
} |
|
|
|
func (api *channellingAPI) HandleUsers(session *Session) (sessions *DataSessions, err error) { |
|
if session.Hello { |
|
sessions = &DataSessions{Type: "Users", Users: api.RoomUsers(session)} |
|
} else { |
|
err = NewDataError("not_in_room", "Cannot list users without a current room") |
|
} |
|
return |
|
} |
|
|
|
func (api *channellingAPI) HandleAuthentication(session *Session, st *SessionToken) (*DataSelf, error) { |
|
if err := api.Authenticate(session, st, ""); err != nil { |
|
log.Println("Authentication failed", err, st.Userid, st.Nonce) |
|
return nil, err |
|
} |
|
|
|
log.Println("Authentication success", session.Userid()) |
|
self, err := api.MakeSelf(session) |
|
if err == nil { |
|
session.BroadcastStatus() |
|
} |
|
|
|
return self, err |
|
} |
|
|
|
func (api *channellingAPI) HandleChat(session *Session, to string, chat *DataChatMessage) { |
|
// TODO(longsleep): Limit sent chat messages per incoming connection. |
|
if !chat.NoEcho { |
|
session.Unicast(session.Id, chat) |
|
} |
|
chat.Time = time.Now().Format(time.RFC3339) |
|
if to == "" { |
|
// TODO(longsleep): Check if chat broadcast is allowed. |
|
if session.Hello { |
|
api.CountBroadcastChat() |
|
session.Broadcast(chat) |
|
} |
|
} else { |
|
if chat.Status != nil { |
|
if chat.Status.ContactRequest != nil { |
|
if !api.Config.WithModule("contacts") { |
|
return |
|
} |
|
if err := api.contactrequestHandler(session, to, chat.Status.ContactRequest); err != nil { |
|
log.Println("Ignoring invalid contact request.", err) |
|
return |
|
} |
|
chat.Status.ContactRequest.Userid = session.Userid() |
|
} |
|
} else { |
|
api.CountUnicastChat() |
|
} |
|
|
|
session.Unicast(to, chat) |
|
if chat.Mid != "" { |
|
// Send out delivery confirmation status chat message. |
|
session.Unicast(session.Id, &DataChat{To: to, Type: "Chat", Chat: &DataChatMessage{Mid: chat.Mid, Status: &DataChatStatus{State: "sent"}}}) |
|
} |
|
} |
|
} |
|
|
|
func (api *channellingAPI) HandleConference(session *Session, conference *DataConference) { |
|
// Check conference maximum size. |
|
if len(conference.Conference) > maxConferenceSize { |
|
log.Println("Refusing to create conference above limit.", len(conference.Conference)) |
|
return |
|
} |
|
|
|
// Send conference update to anyone. |
|
for _, id := range conference.Conference { |
|
if id != session.Id { |
|
session.Unicast(id, conference) |
|
} |
|
} |
|
} |
|
|
|
func (api *channellingAPI) HandleSessions(session *Session, sessions *DataSessionsRequest) (*DataSessions, error) { |
|
switch sessions.Type { |
|
case "contact": |
|
if !api.Config.WithModule("contacts") { |
|
return nil, NewDataError("contacts_not_enabled", "incoming contacts session request with contacts disabled") |
|
} |
|
userID, err := api.getContactID(session, sessions.Token) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return &DataSessions{ |
|
Type: "Sessions", |
|
Users: api.GetUserSessions(session, userID), |
|
Sessions: sessions, |
|
}, nil |
|
case "session": |
|
id, err := session.attestation.Decode(sessions.Token) |
|
if err != nil { |
|
return nil, NewDataError("bad_attestation", err.Error()) |
|
} |
|
session, ok := api.GetSession(id) |
|
if !ok { |
|
return nil, NewDataError("no_such_session", "cannot retrieve session") |
|
} |
|
return &DataSessions{ |
|
Type: "Sessions", |
|
Users: []*DataSession{session.Data()}, |
|
Sessions: sessions, |
|
}, nil |
|
default: |
|
return nil, NewDataError("bad_request", "unknown sessions request type") |
|
} |
|
} |
|
|
|
func (api *channellingAPI) HandleRoom(session *Session, room *DataRoom) (*DataRoom, error) { |
|
room, err := api.UpdateRoom(session, room) |
|
if err == nil { |
|
session.Broadcast(room) |
|
} |
|
return room, err |
|
}
|
|
|