1/**
2 * Standalone signaling server for the Nextcloud Spreed app.
3 * Copyright (C) 2017 struktur AG
4 *
5 * @author Joachim Bauch <bauch@struktur.de>
6 *
7 * @license GNU AGPL version 3 or any later version
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU Affero General Public License for more details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22package signaling
23
24import (
25	"encoding/json"
26	"fmt"
27	"net/url"
28	"strings"
29)
30
31const (
32	// Version that must be sent in a "hello" message.
33	HelloVersion = "1.0"
34)
35
36// ClientMessage is a message that is sent from a client to the server.
37type ClientMessage struct {
38	// The unique request id (optional).
39	Id string `json:"id,omitempty"`
40
41	// The type of the request.
42	Type string `json:"type"`
43
44	// Filled for type "hello"
45	Hello *HelloClientMessage `json:"hello,omitempty"`
46
47	Bye *ByeClientMessage `json:"bye,omitempty"`
48
49	Room *RoomClientMessage `json:"room,omitempty"`
50
51	Message *MessageClientMessage `json:"message,omitempty"`
52
53	Control *ControlClientMessage `json:"control,omitempty"`
54
55	Internal *InternalClientMessage `json:"internal,omitempty"`
56}
57
58func (m *ClientMessage) CheckValid() error {
59	switch m.Type {
60	case "":
61		return fmt.Errorf("type missing")
62	case "hello":
63		if m.Hello == nil {
64			return fmt.Errorf("hello missing")
65		} else if err := m.Hello.CheckValid(); err != nil {
66			return err
67		}
68	case "bye":
69		// No additional check required.
70	case "room":
71		if m.Room == nil {
72			return fmt.Errorf("room missing")
73		} else if err := m.Room.CheckValid(); err != nil {
74			return err
75		}
76	case "message":
77		if m.Message == nil {
78			return fmt.Errorf("message missing")
79		} else if err := m.Message.CheckValid(); err != nil {
80			return err
81		}
82	case "control":
83		if m.Control == nil {
84			return fmt.Errorf("control missing")
85		} else if err := m.Control.CheckValid(); err != nil {
86			return err
87		}
88	case "internal":
89		if m.Internal == nil {
90			return fmt.Errorf("internal missing")
91		} else if err := m.Internal.CheckValid(); err != nil {
92			return err
93		}
94	}
95	return nil
96}
97
98func (m *ClientMessage) NewErrorServerMessage(e *Error) *ServerMessage {
99	return &ServerMessage{
100		Id:    m.Id,
101		Type:  "error",
102		Error: e,
103	}
104}
105
106func (m *ClientMessage) NewWrappedErrorServerMessage(e error) *ServerMessage {
107	return m.NewErrorServerMessage(NewError("internal_error", e.Error()))
108}
109
110// ServerMessage is a message that is sent from the server to a client.
111type ServerMessage struct {
112	Id string `json:"id,omitempty"`
113
114	Type string `json:"type"`
115
116	Error *Error `json:"error,omitempty"`
117
118	Hello *HelloServerMessage `json:"hello,omitempty"`
119
120	Bye *ByeServerMessage `json:"bye,omitempty"`
121
122	Room *RoomServerMessage `json:"room,omitempty"`
123
124	Message *MessageServerMessage `json:"message,omitempty"`
125
126	Control *ControlServerMessage `json:"control,omitempty"`
127
128	Event *EventServerMessage `json:"event,omitempty"`
129}
130
131func (r *ServerMessage) CloseAfterSend(session Session) bool {
132	if r.Type == "bye" {
133		return true
134	}
135
136	if r.Type == "event" {
137		if evt := r.Event; evt != nil && evt.Target == "roomlist" && evt.Type == "disinvite" {
138			// Only close session / connection if the disinvite was for the room
139			// the session is currently in.
140			if session != nil && evt.Disinvite != nil {
141				if room := session.GetRoom(); room != nil && evt.Disinvite.RoomId == room.Id() {
142					return true
143				}
144			}
145		}
146	}
147
148	return false
149}
150
151func (r *ServerMessage) IsChatRefresh() bool {
152	if r.Type != "message" || r.Message == nil || r.Message.Data == nil || len(*r.Message.Data) == 0 {
153		return false
154	}
155
156	var data MessageServerMessageData
157	if err := json.Unmarshal(*r.Message.Data, &data); err != nil {
158		return false
159	}
160
161	if data.Type != "chat" || data.Chat == nil {
162		return false
163	}
164
165	return data.Chat.Refresh
166}
167
168func (r *ServerMessage) IsParticipantsUpdate() bool {
169	if r.Type != "event" || r.Event == nil {
170		return false
171	}
172	if event := r.Event; event.Target != "participants" || event.Type != "update" {
173		return false
174	}
175	return true
176}
177
178type Error struct {
179	Code    string      `json:"code"`
180	Message string      `json:"message"`
181	Details interface{} `json:"details,omitempty"`
182}
183
184func NewError(code string, message string) *Error {
185	return NewErrorDetail(code, message, nil)
186}
187
188func NewErrorDetail(code string, message string, details interface{}) *Error {
189	return &Error{
190		Code:    code,
191		Message: message,
192		Details: details,
193	}
194}
195
196func (e *Error) Error() string {
197	return e.Message
198}
199
200const (
201	HelloClientTypeClient   = "client"
202	HelloClientTypeInternal = "internal"
203
204	HelloClientTypeVirtual = "virtual"
205)
206
207func hasStandardPort(u *url.URL) bool {
208	switch u.Scheme {
209	case "http":
210		return u.Port() == "80"
211	case "https":
212		return u.Port() == "443"
213	default:
214		return false
215	}
216}
217
218type ClientTypeInternalAuthParams struct {
219	Random string `json:"random"`
220	Token  string `json:"token"`
221
222	Backend       string `json:"backend"`
223	parsedBackend *url.URL
224}
225
226func (p *ClientTypeInternalAuthParams) CheckValid() error {
227	if p.Backend == "" {
228		return fmt.Errorf("backend missing")
229	} else if u, err := url.Parse(p.Backend); err != nil {
230		return err
231	} else {
232		if strings.Contains(u.Host, ":") && hasStandardPort(u) {
233			u.Host = u.Hostname()
234		}
235
236		p.parsedBackend = u
237	}
238	return nil
239}
240
241type HelloClientMessageAuth struct {
242	// The client type that is connecting. Leave empty to use the default
243	// "HelloClientTypeClient"
244	Type string `json:"type,omitempty"`
245
246	Params *json.RawMessage `json:"params"`
247
248	Url       string `json:"url"`
249	parsedUrl *url.URL
250
251	internalParams ClientTypeInternalAuthParams
252}
253
254// Type "hello"
255
256type HelloClientMessage struct {
257	Version string `json:"version"`
258
259	ResumeId string `json:"resumeid"`
260
261	Features []string `json:"features,omitempty"`
262
263	// The authentication credentials.
264	Auth HelloClientMessageAuth `json:"auth"`
265}
266
267func (m *HelloClientMessage) CheckValid() error {
268	if m.Version != HelloVersion {
269		return fmt.Errorf("unsupported hello version: %s", m.Version)
270	}
271	if m.ResumeId == "" {
272		if m.Auth.Params == nil || len(*m.Auth.Params) == 0 {
273			return fmt.Errorf("params missing")
274		}
275		if m.Auth.Type == "" {
276			m.Auth.Type = HelloClientTypeClient
277		}
278		switch m.Auth.Type {
279		case HelloClientTypeClient:
280			if m.Auth.Url == "" {
281				return fmt.Errorf("url missing")
282			} else if u, err := url.ParseRequestURI(m.Auth.Url); err != nil {
283				return err
284			} else {
285				if strings.Contains(u.Host, ":") && hasStandardPort(u) {
286					u.Host = u.Hostname()
287				}
288
289				m.Auth.parsedUrl = u
290			}
291		case HelloClientTypeInternal:
292			if err := json.Unmarshal(*m.Auth.Params, &m.Auth.internalParams); err != nil {
293				return err
294			} else if err := m.Auth.internalParams.CheckValid(); err != nil {
295				return err
296			}
297		default:
298			return fmt.Errorf("unsupported auth type")
299		}
300	}
301	return nil
302}
303
304const (
305	// Features for all clients.
306	ServerFeatureMcu = "mcu"
307
308	// Features for internal clients only.
309	ServerFeatureInternalVirtualSessions = "virtual-sessions"
310)
311
312var (
313	DefaultFeatures         []string
314	DefaultFeaturesInternal []string = []string{
315		ServerFeatureInternalVirtualSessions,
316	}
317)
318
319type HelloServerMessageServer struct {
320	Version  string   `json:"version"`
321	Features []string `json:"features,omitempty"`
322	Country  string   `json:"country,omitempty"`
323}
324
325type HelloServerMessage struct {
326	Version string `json:"version"`
327
328	SessionId string                    `json:"sessionid"`
329	ResumeId  string                    `json:"resumeid"`
330	UserId    string                    `json:"userid"`
331	Server    *HelloServerMessageServer `json:"server,omitempty"`
332}
333
334// Type "bye"
335
336type ByeClientMessage struct {
337}
338
339func (m *ByeClientMessage) CheckValid() error {
340	// No additional validation required.
341	return nil
342}
343
344type ByeServerMessage struct {
345	Reason string `json:"reason"`
346}
347
348// Type "room"
349
350type RoomClientMessage struct {
351	RoomId    string `json:"roomid"`
352	SessionId string `json:"sessionid,omitempty"`
353}
354
355func (m *RoomClientMessage) CheckValid() error {
356	// No additional validation required.
357	return nil
358}
359
360type RoomServerMessage struct {
361	RoomId     string           `json:"roomid"`
362	Properties *json.RawMessage `json:"properties,omitempty"`
363}
364
365// Type "message"
366
367const (
368	RecipientTypeSession = "session"
369	RecipientTypeUser    = "user"
370	RecipientTypeRoom    = "room"
371)
372
373type MessageClientMessageRecipient struct {
374	Type string `json:"type"`
375
376	SessionId string `json:"sessionid,omitempty"`
377	UserId    string `json:"userid,omitempty"`
378}
379
380type MessageClientMessage struct {
381	Recipient MessageClientMessageRecipient `json:"recipient"`
382
383	Data *json.RawMessage `json:"data"`
384}
385
386type MessageClientMessageData struct {
387	Type     string                 `json:"type"`
388	Sid      string                 `json:"sid"`
389	RoomType string                 `json:"roomType"`
390	Payload  map[string]interface{} `json:"payload"`
391}
392
393func (m *MessageClientMessage) CheckValid() error {
394	if m.Data == nil || len(*m.Data) == 0 {
395		return fmt.Errorf("message empty")
396	}
397	switch m.Recipient.Type {
398	case RecipientTypeRoom:
399		// No additional checks required.
400	case RecipientTypeSession:
401		if m.Recipient.SessionId == "" {
402			return fmt.Errorf("session id missing")
403		}
404	case RecipientTypeUser:
405		if m.Recipient.UserId == "" {
406			return fmt.Errorf("user id missing")
407		}
408	default:
409		return fmt.Errorf("unsupported recipient type %v", m.Recipient.Type)
410	}
411	return nil
412}
413
414type MessageServerMessageSender struct {
415	Type string `json:"type"`
416
417	SessionId string `json:"sessionid,omitempty"`
418	UserId    string `json:"userid,omitempty"`
419}
420
421type MessageServerMessageDataChat struct {
422	Refresh bool `json:"refresh"`
423}
424
425type MessageServerMessageData struct {
426	Type string `json:"type"`
427
428	Chat *MessageServerMessageDataChat `json:"chat,omitempty"`
429}
430
431type MessageServerMessage struct {
432	Sender    *MessageServerMessageSender    `json:"sender"`
433	Recipient *MessageClientMessageRecipient `json:"recipient,omitempty"`
434
435	Data *json.RawMessage `json:"data"`
436}
437
438// Type "control"
439
440type ControlClientMessage struct {
441	MessageClientMessage
442}
443
444func (m *ControlClientMessage) CheckValid() error {
445	if err := m.MessageClientMessage.CheckValid(); err != nil {
446		return err
447	}
448	return nil
449}
450
451type ControlServerMessage struct {
452	Sender    *MessageServerMessageSender    `json:"sender"`
453	Recipient *MessageClientMessageRecipient `json:"recipient,omitempty"`
454
455	Data *json.RawMessage `json:"data"`
456}
457
458// Type "internal"
459
460type CommonSessionInternalClientMessage struct {
461	SessionId string `json:"sessionid"`
462
463	RoomId string `json:"roomid"`
464}
465
466func (m *CommonSessionInternalClientMessage) CheckValid() error {
467	if m.SessionId == "" {
468		return fmt.Errorf("sessionid missing")
469	}
470	if m.RoomId == "" {
471		return fmt.Errorf("roomid missing")
472	}
473	return nil
474}
475
476type AddSessionOptions struct {
477	ActorId   string `json:"actorId,omitempty"`
478	ActorType string `json:"actorType,omitempty"`
479}
480
481type AddSessionInternalClientMessage struct {
482	CommonSessionInternalClientMessage
483
484	UserId string           `json:"userid,omitempty"`
485	User   *json.RawMessage `json:"user,omitempty"`
486	Flags  uint32           `json:"flags,omitempty"`
487
488	Options *AddSessionOptions `json:"options,omitempy"`
489}
490
491func (m *AddSessionInternalClientMessage) CheckValid() error {
492	if err := m.CommonSessionInternalClientMessage.CheckValid(); err != nil {
493		return err
494	}
495	return nil
496}
497
498type UpdateSessionInternalClientMessage struct {
499	CommonSessionInternalClientMessage
500
501	Flags *uint32 `json:"flags,omitempty"`
502}
503
504func (m *UpdateSessionInternalClientMessage) CheckValid() error {
505	if err := m.CommonSessionInternalClientMessage.CheckValid(); err != nil {
506		return err
507	}
508	return nil
509}
510
511type RemoveSessionInternalClientMessage struct {
512	CommonSessionInternalClientMessage
513
514	UserId string `json:"userid,omitempty"`
515}
516
517func (m *RemoveSessionInternalClientMessage) CheckValid() error {
518	if err := m.CommonSessionInternalClientMessage.CheckValid(); err != nil {
519		return err
520	}
521	return nil
522}
523
524type InternalClientMessage struct {
525	Type string `json:"type"`
526
527	AddSession *AddSessionInternalClientMessage `json:"addsession,omitempty"`
528
529	UpdateSession *UpdateSessionInternalClientMessage `json:"updatesession,omitempty"`
530
531	RemoveSession *RemoveSessionInternalClientMessage `json:"removesession,omitempty"`
532}
533
534func (m *InternalClientMessage) CheckValid() error {
535	switch m.Type {
536	case "addsession":
537		if m.AddSession == nil {
538			return fmt.Errorf("addsession missing")
539		} else if err := m.AddSession.CheckValid(); err != nil {
540			return err
541		}
542	case "updatesession":
543		if m.UpdateSession == nil {
544			return fmt.Errorf("updatesession missing")
545		} else if err := m.UpdateSession.CheckValid(); err != nil {
546			return err
547		}
548	case "removesession":
549		if m.RemoveSession == nil {
550			return fmt.Errorf("removesession missing")
551		} else if err := m.RemoveSession.CheckValid(); err != nil {
552			return err
553		}
554	}
555	return nil
556}
557
558// Type "event"
559
560type RoomEventServerMessage struct {
561	RoomId     string           `json:"roomid"`
562	Properties *json.RawMessage `json:"properties,omitempty"`
563	// TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk.
564	InCall  *json.RawMessage         `json:"incall,omitempty"`
565	Changed []map[string]interface{} `json:"changed,omitempty"`
566	Users   []map[string]interface{} `json:"users,omitempty"`
567}
568
569const (
570	DisinviteReasonDisinvited = "disinvited"
571	DisinviteReasonDeleted    = "deleted"
572)
573
574type RoomDisinviteEventServerMessage struct {
575	RoomEventServerMessage
576
577	Reason string `json:"reason"`
578}
579
580type RoomEventMessage struct {
581	RoomId string           `json:"roomid"`
582	Data   *json.RawMessage `json:"data,omitempty"`
583}
584
585type RoomFlagsServerMessage struct {
586	RoomId    string `json:"roomid"`
587	SessionId string `json:"sessionid"`
588	Flags     uint32 `json:"flags"`
589}
590
591type EventServerMessage struct {
592	Target string `json:"target"`
593	Type   string `json:"type"`
594
595	// Used for target "room"
596	Join   []*EventServerMessageSessionEntry `json:"join,omitempty"`
597	Leave  []string                          `json:"leave,omitempty"`
598	Change []*EventServerMessageSessionEntry `json:"change,omitempty"`
599
600	// Used for target "roomlist" / "participants"
601	Invite    *RoomEventServerMessage          `json:"invite,omitempty"`
602	Disinvite *RoomDisinviteEventServerMessage `json:"disinvite,omitempty"`
603	Update    *RoomEventServerMessage          `json:"update,omitempty"`
604	Flags     *RoomFlagsServerMessage          `json:"flags,omitempty"`
605
606	// Used for target "message"
607	Message *RoomEventMessage `json:"message,omitempty"`
608}
609
610type EventServerMessageSessionEntry struct {
611	SessionId string           `json:"sessionid"`
612	UserId    string           `json:"userid"`
613	User      *json.RawMessage `json:"user,omitempty"`
614}
615
616// MCU-related types
617
618type AnswerOfferMessage struct {
619	To       string                 `json:"to"`
620	From     string                 `json:"from"`
621	Type     string                 `json:"type"`
622	RoomType string                 `json:"roomType"`
623	Payload  map[string]interface{} `json:"payload"`
624}
625