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