1/**
2 * Standalone signaling server for the Nextcloud Spreed app.
3 * Copyright (C) 2019 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	"testing"
27
28	"golang.org/x/net/context"
29)
30
31func TestVirtualSession(t *testing.T) {
32	hub, _, _, server, shutdown := CreateHubForTest(t)
33	defer shutdown()
34
35	roomId := "the-room-id"
36	emptyProperties := json.RawMessage("{}")
37	backend := &Backend{
38		id:     "compat",
39		compat: true,
40	}
41	room, err := hub.createRoom(roomId, &emptyProperties, backend)
42	if err != nil {
43		t.Fatalf("Could not create room: %s", err)
44	}
45	defer room.Close()
46
47	clientInternal := NewTestClient(t, server, hub)
48	defer clientInternal.CloseWithBye()
49	if err := clientInternal.SendHelloInternal(); err != nil {
50		t.Fatal(err)
51	}
52
53	client := NewTestClient(t, server, hub)
54	defer client.CloseWithBye()
55	if err := client.SendHello(testDefaultUserId); err != nil {
56		t.Fatal(err)
57	}
58
59	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
60	defer cancel()
61
62	if hello, err := clientInternal.RunUntilHello(ctx); err != nil {
63		t.Error(err)
64	} else {
65		if hello.Hello.UserId != "" {
66			t.Errorf("Expected empty user id, got %+v", hello.Hello)
67		}
68		if hello.Hello.SessionId == "" {
69			t.Errorf("Expected session id, got %+v", hello.Hello)
70		}
71		if hello.Hello.ResumeId == "" {
72			t.Errorf("Expected resume id, got %+v", hello.Hello)
73		}
74	}
75	hello, err := client.RunUntilHello(ctx)
76	if err != nil {
77		t.Error(err)
78	}
79
80	if room, err := client.JoinRoom(ctx, roomId); err != nil {
81		t.Fatal(err)
82	} else if room.Room.RoomId != roomId {
83		t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
84	}
85
86	// Ignore "join" events.
87	if err := client.DrainMessages(ctx); err != nil {
88		t.Error(err)
89	}
90
91	internalSessionId := "session1"
92	userId := "user1"
93	msgAdd := &ClientMessage{
94		Type: "internal",
95		Internal: &InternalClientMessage{
96			Type: "addsession",
97			AddSession: &AddSessionInternalClientMessage{
98				CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{
99					SessionId: internalSessionId,
100					RoomId:    roomId,
101				},
102				UserId: userId,
103				Flags:  FLAG_MUTED_SPEAKING,
104			},
105		},
106	}
107	if err := clientInternal.WriteJSON(msgAdd); err != nil {
108		t.Fatal(err)
109	}
110
111	msg1, err := client.RunUntilMessage(ctx)
112	if err != nil {
113		t.Fatal(err)
114	}
115	// The public session id will be generated by the server, so don't check for it.
116	if err := client.checkMessageJoinedSession(msg1, "", userId); err != nil {
117		t.Fatal(err)
118	}
119	sessionId := msg1.Event.Join[0].SessionId
120	session := hub.GetSessionByPublicId(sessionId)
121	if session == nil {
122		t.Fatalf("Could not get virtual session %s", sessionId)
123	}
124	if session.ClientType() != HelloClientTypeVirtual {
125		t.Errorf("Expected client type %s, got %s", HelloClientTypeVirtual, session.ClientType())
126	}
127	if sid := session.(*VirtualSession).SessionId(); sid != internalSessionId {
128		t.Errorf("Expected internal session id %s, got %s", internalSessionId, sid)
129	}
130
131	// Also a participants update event will be triggered for the virtual user.
132	msg2, err := client.RunUntilMessage(ctx)
133	if err != nil {
134		t.Fatal(err)
135	}
136	updateMsg, err := checkMessageParticipantsInCall(msg2)
137	if err != nil {
138		t.Error(err)
139	} else if updateMsg.RoomId != roomId {
140		t.Errorf("Expected room %s, got %s", roomId, updateMsg.RoomId)
141	} else if len(updateMsg.Users) != 1 {
142		t.Errorf("Expected one user, got %+v", updateMsg.Users)
143	} else if sid, ok := updateMsg.Users[0]["sessionId"].(string); !ok || sid != sessionId {
144		t.Errorf("Expected session id %s, got %+v", sessionId, updateMsg.Users[0])
145	} else if virtual, ok := updateMsg.Users[0]["virtual"].(bool); !ok || !virtual {
146		t.Errorf("Expected virtual user, got %+v", updateMsg.Users[0])
147	} else if inCall, ok := updateMsg.Users[0]["inCall"].(float64); !ok || inCall == 0 {
148		t.Errorf("Expected user in call, got %+v", updateMsg.Users[0])
149	}
150
151	msg3, err := client.RunUntilMessage(ctx)
152	if err != nil {
153		t.Fatal(err)
154	}
155
156	flagsMsg, err := checkMessageParticipantFlags(msg3)
157	if err != nil {
158		t.Error(err)
159	} else if flagsMsg.RoomId != roomId {
160		t.Errorf("Expected room %s, got %s", roomId, flagsMsg.RoomId)
161	} else if flagsMsg.SessionId != sessionId {
162		t.Errorf("Expected session id %s, got %s", sessionId, flagsMsg.SessionId)
163	} else if flagsMsg.Flags != FLAG_MUTED_SPEAKING {
164		t.Errorf("Expected flags %d, got %+v", FLAG_MUTED_SPEAKING, flagsMsg.Flags)
165	}
166
167	newFlags := uint32(FLAG_TALKING)
168	msgFlags := &ClientMessage{
169		Type: "internal",
170		Internal: &InternalClientMessage{
171			Type: "updatesession",
172			UpdateSession: &UpdateSessionInternalClientMessage{
173				CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{
174					SessionId: internalSessionId,
175					RoomId:    roomId,
176				},
177				Flags: &newFlags,
178			},
179		},
180	}
181	if err := clientInternal.WriteJSON(msgFlags); err != nil {
182		t.Fatal(err)
183	}
184
185	msg4, err := client.RunUntilMessage(ctx)
186	if err != nil {
187		t.Fatal(err)
188	}
189
190	flagsMsg, err = checkMessageParticipantFlags(msg4)
191	if err != nil {
192		t.Error(err)
193	} else if flagsMsg.RoomId != roomId {
194		t.Errorf("Expected room %s, got %s", roomId, flagsMsg.RoomId)
195	} else if flagsMsg.SessionId != sessionId {
196		t.Errorf("Expected session id %s, got %s", sessionId, flagsMsg.SessionId)
197	} else if flagsMsg.Flags != newFlags {
198		t.Errorf("Expected flags %d, got %+v", newFlags, flagsMsg.Flags)
199	}
200
201	// When sending to a virtual session, the message is sent to the actual
202	// client and contains a "Recipient" block with the internal session id.
203	recipient := MessageClientMessageRecipient{
204		Type:      "session",
205		SessionId: sessionId,
206	}
207
208	data := "from-client-to-virtual"
209	client.SendMessage(recipient, data)
210
211	msg2, err = clientInternal.RunUntilMessage(ctx)
212	if err != nil {
213		t.Fatal(err)
214	} else if err := checkMessageType(msg2, "message"); err != nil {
215		t.Fatal(err)
216	} else if err := checkMessageSender(hub, msg2.Message, "session", hello.Hello); err != nil {
217		t.Error(err)
218	}
219
220	if msg2.Message.Recipient == nil {
221		t.Errorf("Expected recipient, got none")
222	} else if msg2.Message.Recipient.Type != "session" {
223		t.Errorf("Expected recipient type session, got %s", msg2.Message.Recipient.Type)
224	} else if msg2.Message.Recipient.SessionId != internalSessionId {
225		t.Errorf("Expected recipient %s, got %s", internalSessionId, msg2.Message.Recipient.SessionId)
226	}
227
228	var payload string
229	if err := json.Unmarshal(*msg2.Message.Data, &payload); err != nil {
230		t.Error(err)
231	} else if payload != data {
232		t.Errorf("Expected payload %s, got %s", data, payload)
233	}
234
235	msgRemove := &ClientMessage{
236		Type: "internal",
237		Internal: &InternalClientMessage{
238			Type: "removesession",
239			RemoveSession: &RemoveSessionInternalClientMessage{
240				CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{
241					SessionId: internalSessionId,
242					RoomId:    roomId,
243				},
244			},
245		},
246	}
247	if err := clientInternal.WriteJSON(msgRemove); err != nil {
248		t.Fatal(err)
249	}
250
251	msg5, err := client.RunUntilMessage(ctx)
252	if err != nil {
253		t.Fatal(err)
254	}
255	if err := client.checkMessageRoomLeaveSession(msg5, sessionId); err != nil {
256		t.Error(err)
257	}
258}
259
260func TestVirtualSessionCleanup(t *testing.T) {
261	hub, _, _, server, shutdown := CreateHubForTest(t)
262	defer shutdown()
263
264	roomId := "the-room-id"
265	emptyProperties := json.RawMessage("{}")
266	backend := &Backend{
267		id:     "compat",
268		compat: true,
269	}
270	room, err := hub.createRoom(roomId, &emptyProperties, backend)
271	if err != nil {
272		t.Fatalf("Could not create room: %s", err)
273	}
274	defer room.Close()
275
276	clientInternal := NewTestClient(t, server, hub)
277	defer clientInternal.CloseWithBye()
278	if err := clientInternal.SendHelloInternal(); err != nil {
279		t.Fatal(err)
280	}
281
282	client := NewTestClient(t, server, hub)
283	defer client.CloseWithBye()
284	if err := client.SendHello(testDefaultUserId); err != nil {
285		t.Fatal(err)
286	}
287
288	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
289	defer cancel()
290
291	if hello, err := clientInternal.RunUntilHello(ctx); err != nil {
292		t.Error(err)
293	} else {
294		if hello.Hello.UserId != "" {
295			t.Errorf("Expected empty user id, got %+v", hello.Hello)
296		}
297		if hello.Hello.SessionId == "" {
298			t.Errorf("Expected session id, got %+v", hello.Hello)
299		}
300		if hello.Hello.ResumeId == "" {
301			t.Errorf("Expected resume id, got %+v", hello.Hello)
302		}
303	}
304	if _, err := client.RunUntilHello(ctx); err != nil {
305		t.Error(err)
306	}
307
308	if room, err := client.JoinRoom(ctx, roomId); err != nil {
309		t.Fatal(err)
310	} else if room.Room.RoomId != roomId {
311		t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
312	}
313
314	// Ignore "join" events.
315	if err := client.DrainMessages(ctx); err != nil {
316		t.Error(err)
317	}
318
319	internalSessionId := "session1"
320	userId := "user1"
321	msgAdd := &ClientMessage{
322		Type: "internal",
323		Internal: &InternalClientMessage{
324			Type: "addsession",
325			AddSession: &AddSessionInternalClientMessage{
326				CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{
327					SessionId: internalSessionId,
328					RoomId:    roomId,
329				},
330				UserId: userId,
331				Flags:  FLAG_MUTED_SPEAKING,
332			},
333		},
334	}
335	if err := clientInternal.WriteJSON(msgAdd); err != nil {
336		t.Fatal(err)
337	}
338
339	msg1, err := client.RunUntilMessage(ctx)
340	if err != nil {
341		t.Fatal(err)
342	}
343	// The public session id will be generated by the server, so don't check for it.
344	if err := client.checkMessageJoinedSession(msg1, "", userId); err != nil {
345		t.Fatal(err)
346	}
347	sessionId := msg1.Event.Join[0].SessionId
348	session := hub.GetSessionByPublicId(sessionId)
349	if session == nil {
350		t.Fatalf("Could not get virtual session %s", sessionId)
351	}
352	if session.ClientType() != HelloClientTypeVirtual {
353		t.Errorf("Expected client type %s, got %s", HelloClientTypeVirtual, session.ClientType())
354	}
355	if sid := session.(*VirtualSession).SessionId(); sid != internalSessionId {
356		t.Errorf("Expected internal session id %s, got %s", internalSessionId, sid)
357	}
358
359	// Also a participants update event will be triggered for the virtual user.
360	msg2, err := client.RunUntilMessage(ctx)
361	if err != nil {
362		t.Fatal(err)
363	}
364	updateMsg, err := checkMessageParticipantsInCall(msg2)
365	if err != nil {
366		t.Error(err)
367	} else if updateMsg.RoomId != roomId {
368		t.Errorf("Expected room %s, got %s", roomId, updateMsg.RoomId)
369	} else if len(updateMsg.Users) != 1 {
370		t.Errorf("Expected one user, got %+v", updateMsg.Users)
371	} else if sid, ok := updateMsg.Users[0]["sessionId"].(string); !ok || sid != sessionId {
372		t.Errorf("Expected session id %s, got %+v", sessionId, updateMsg.Users[0])
373	} else if virtual, ok := updateMsg.Users[0]["virtual"].(bool); !ok || !virtual {
374		t.Errorf("Expected virtual user, got %+v", updateMsg.Users[0])
375	} else if inCall, ok := updateMsg.Users[0]["inCall"].(float64); !ok || inCall == 0 {
376		t.Errorf("Expected user in call, got %+v", updateMsg.Users[0])
377	}
378
379	msg3, err := client.RunUntilMessage(ctx)
380	if err != nil {
381		t.Fatal(err)
382	}
383
384	flagsMsg, err := checkMessageParticipantFlags(msg3)
385	if err != nil {
386		t.Error(err)
387	} else if flagsMsg.RoomId != roomId {
388		t.Errorf("Expected room %s, got %s", roomId, flagsMsg.RoomId)
389	} else if flagsMsg.SessionId != sessionId {
390		t.Errorf("Expected session id %s, got %s", sessionId, flagsMsg.SessionId)
391	} else if flagsMsg.Flags != FLAG_MUTED_SPEAKING {
392		t.Errorf("Expected flags %d, got %+v", FLAG_MUTED_SPEAKING, flagsMsg.Flags)
393	}
394
395	// The virtual sessions are closed when the parent session is deleted.
396	clientInternal.CloseWithBye()
397
398	if msg2, err := client.RunUntilMessage(ctx); err != nil {
399		t.Fatal(err)
400	} else if err := client.checkMessageRoomLeaveSession(msg2, sessionId); err != nil {
401		t.Error(err)
402	}
403}
404
405func TestVirtualSessionFlags(t *testing.T) {
406	s := &VirtualSession{
407		publicId: "dummy-for-testing",
408	}
409	if s.Flags() != 0 {
410		t.Fatalf("Expected flags 0, got %d", s.Flags())
411	}
412	s.AddFlags(1)
413	if s.Flags() != 1 {
414		t.Fatalf("Expected flags 1, got %d", s.Flags())
415	}
416	s.AddFlags(1)
417	if s.Flags() != 1 {
418		t.Fatalf("Expected flags 1, got %d", s.Flags())
419	}
420	s.AddFlags(2)
421	if s.Flags() != 3 {
422		t.Fatalf("Expected flags 3, got %d", s.Flags())
423	}
424	s.RemoveFlags(1)
425	if s.Flags() != 2 {
426		t.Fatalf("Expected flags 2, got %d", s.Flags())
427	}
428	s.RemoveFlags(1)
429	if s.Flags() != 2 {
430		t.Fatalf("Expected flags 2, got %d", s.Flags())
431	}
432	s.AddFlags(3)
433	if s.Flags() != 3 {
434		t.Fatalf("Expected flags 3, got %d", s.Flags())
435	}
436	s.RemoveFlags(1)
437	if s.Flags() != 2 {
438		t.Fatalf("Expected flags 2, got %d", s.Flags())
439	}
440}
441