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