mirror of
https://github.com/strukturag/nextcloud-spreed-signaling
synced 2024-06-01 21:42:18 +02:00
Send initial "welcome" message when clients connect.
This can be used to detect server features before performing the actual "hello" handshake.
This commit is contained in:
parent
32a2f822e0
commit
f7db8a38e1
|
@ -157,7 +157,7 @@ type HelloProxyServerMessage struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
|
||||||
SessionId string `json:"sessionid"`
|
SessionId string `json:"sessionid"`
|
||||||
Server *HelloServerMessageServer `json:"server,omitempty"`
|
Server *WelcomeServerMessage `json:"server,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type "bye"
|
// Type "bye"
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -141,6 +142,8 @@ type ServerMessage struct {
|
||||||
|
|
||||||
Error *Error `json:"error,omitempty"`
|
Error *Error `json:"error,omitempty"`
|
||||||
|
|
||||||
|
Welcome *WelcomeServerMessage `json:"welcome,omitempty"`
|
||||||
|
|
||||||
Hello *HelloServerMessage `json:"hello,omitempty"`
|
Hello *HelloServerMessage `json:"hello,omitempty"`
|
||||||
|
|
||||||
Bye *ByeServerMessage `json:"bye,omitempty"`
|
Bye *ByeServerMessage `json:"bye,omitempty"`
|
||||||
|
@ -233,6 +236,54 @@ func (e *Error) Error() string {
|
||||||
return e.Message
|
return e.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WelcomeServerMessage struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Features []string `json:"features,omitempty"`
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWelcomeServerMessage(version string, feature ...string) *WelcomeServerMessage {
|
||||||
|
message := &WelcomeServerMessage{
|
||||||
|
Version: version,
|
||||||
|
Features: feature,
|
||||||
|
}
|
||||||
|
if len(feature) > 0 {
|
||||||
|
sort.Strings(message.Features)
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *WelcomeServerMessage) AddFeature(feature ...string) {
|
||||||
|
newFeatures := make([]string, len(m.Features))
|
||||||
|
copy(newFeatures, m.Features)
|
||||||
|
for _, feat := range feature {
|
||||||
|
found := false
|
||||||
|
for _, f := range newFeatures {
|
||||||
|
if f == feat {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
newFeatures = append(newFeatures, feat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(newFeatures)
|
||||||
|
m.Features = newFeatures
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *WelcomeServerMessage) RemoveFeature(feature ...string) {
|
||||||
|
newFeatures := make([]string, len(m.Features))
|
||||||
|
copy(newFeatures, m.Features)
|
||||||
|
for _, feat := range feature {
|
||||||
|
idx := sort.SearchStrings(newFeatures, feat)
|
||||||
|
if idx < len(newFeatures) && newFeatures[idx] == feat {
|
||||||
|
newFeatures = append(newFeatures[:idx], newFeatures[idx+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.Features = newFeatures
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HelloClientTypeClient = "client"
|
HelloClientTypeClient = "client"
|
||||||
HelloClientTypeInternal = "internal"
|
HelloClientTypeInternal = "internal"
|
||||||
|
@ -345,6 +396,7 @@ const (
|
||||||
ServerFeatureAudioVideoPermissions = "audio-video-permissions"
|
ServerFeatureAudioVideoPermissions = "audio-video-permissions"
|
||||||
ServerFeatureTransientData = "transient-data"
|
ServerFeatureTransientData = "transient-data"
|
||||||
ServerFeatureInCallAll = "incall-all"
|
ServerFeatureInCallAll = "incall-all"
|
||||||
|
ServerFeatureWelcome = "welcome"
|
||||||
|
|
||||||
// Features for internal clients only.
|
// Features for internal clients only.
|
||||||
ServerFeatureInternalVirtualSessions = "virtual-sessions"
|
ServerFeatureInternalVirtualSessions = "virtual-sessions"
|
||||||
|
@ -355,27 +407,32 @@ var (
|
||||||
ServerFeatureAudioVideoPermissions,
|
ServerFeatureAudioVideoPermissions,
|
||||||
ServerFeatureTransientData,
|
ServerFeatureTransientData,
|
||||||
ServerFeatureInCallAll,
|
ServerFeatureInCallAll,
|
||||||
|
ServerFeatureWelcome,
|
||||||
}
|
}
|
||||||
DefaultFeaturesInternal = []string{
|
DefaultFeaturesInternal = []string{
|
||||||
ServerFeatureInternalVirtualSessions,
|
ServerFeatureInternalVirtualSessions,
|
||||||
ServerFeatureTransientData,
|
ServerFeatureTransientData,
|
||||||
ServerFeatureInCallAll,
|
ServerFeatureInCallAll,
|
||||||
|
ServerFeatureWelcome,
|
||||||
|
}
|
||||||
|
DefaultWelcomeFeatures = []string{
|
||||||
|
ServerFeatureAudioVideoPermissions,
|
||||||
|
ServerFeatureInternalVirtualSessions,
|
||||||
|
ServerFeatureTransientData,
|
||||||
|
ServerFeatureInCallAll,
|
||||||
|
ServerFeatureWelcome,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type HelloServerMessageServer struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
Features []string `json:"features,omitempty"`
|
|
||||||
Country string `json:"country,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HelloServerMessage struct {
|
type HelloServerMessage struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
|
||||||
SessionId string `json:"sessionid"`
|
SessionId string `json:"sessionid"`
|
||||||
ResumeId string `json:"resumeid"`
|
ResumeId string `json:"resumeid"`
|
||||||
UserId string `json:"userid"`
|
UserId string `json:"userid"`
|
||||||
Server *HelloServerMessageServer `json:"server,omitempty"`
|
|
||||||
|
// TODO: Remove once all clients have switched to the "welcome" message.
|
||||||
|
Server *WelcomeServerMessage `json:"server,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type "bye"
|
// Type "bye"
|
||||||
|
|
|
@ -24,6 +24,8 @@ package signaling
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -346,3 +348,42 @@ func TestIsChatRefresh(t *testing.T) {
|
||||||
t.Error("message should not be detected as chat refresh")
|
t.Error("message should not be detected as chat refresh")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertEqualStrings(t *testing.T, expected, result []string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if expected == nil {
|
||||||
|
expected = make([]string, 0)
|
||||||
|
} else {
|
||||||
|
sort.Strings(expected)
|
||||||
|
}
|
||||||
|
if result == nil {
|
||||||
|
result = make([]string, 0)
|
||||||
|
} else {
|
||||||
|
sort.Strings(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected, result) {
|
||||||
|
t.Errorf("Expected %+v, got %+v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Welcome_AddRemoveFeature(t *testing.T) {
|
||||||
|
var msg WelcomeServerMessage
|
||||||
|
assertEqualStrings(t, []string{}, msg.Features)
|
||||||
|
|
||||||
|
msg.AddFeature("one", "two", "one")
|
||||||
|
assertEqualStrings(t, []string{"one", "two"}, msg.Features)
|
||||||
|
if !sort.StringsAreSorted(msg.Features) {
|
||||||
|
t.Errorf("features should be sorted, got %+v", msg.Features)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.AddFeature("three")
|
||||||
|
assertEqualStrings(t, []string{"one", "two", "three"}, msg.Features)
|
||||||
|
if !sort.StringsAreSorted(msg.Features) {
|
||||||
|
t.Errorf("features should be sorted, got %+v", msg.Features)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.RemoveFeature("three", "one")
|
||||||
|
assertEqualStrings(t, []string{"two"}, msg.Features)
|
||||||
|
}
|
||||||
|
|
74
hub.go
74
hub.go
|
@ -106,8 +106,9 @@ type Hub struct {
|
||||||
nats NatsClient
|
nats NatsClient
|
||||||
upgrader websocket.Upgrader
|
upgrader websocket.Upgrader
|
||||||
cookie *securecookie.SecureCookie
|
cookie *securecookie.SecureCookie
|
||||||
info *HelloServerMessageServer
|
info *WelcomeServerMessage
|
||||||
infoInternal *HelloServerMessageServer
|
infoInternal *WelcomeServerMessage
|
||||||
|
welcome atomic.Value // *ServerMessage
|
||||||
|
|
||||||
stopped int32
|
stopped int32
|
||||||
stopChan chan bool
|
stopChan chan bool
|
||||||
|
@ -298,14 +299,8 @@ func NewHub(config *goconf.ConfigFile, nats NatsClient, r *mux.Router, version s
|
||||||
WriteBufferSize: websocketWriteBufferSize,
|
WriteBufferSize: websocketWriteBufferSize,
|
||||||
},
|
},
|
||||||
cookie: securecookie.New([]byte(hashKey), blockBytes).MaxAge(0),
|
cookie: securecookie.New([]byte(hashKey), blockBytes).MaxAge(0),
|
||||||
info: &HelloServerMessageServer{
|
info: NewWelcomeServerMessage(version, DefaultFeatures...),
|
||||||
Version: version,
|
infoInternal: NewWelcomeServerMessage(version, DefaultFeaturesInternal...),
|
||||||
Features: DefaultFeatures,
|
|
||||||
},
|
|
||||||
infoInternal: &HelloServerMessageServer{
|
|
||||||
Version: version,
|
|
||||||
Features: DefaultFeaturesInternal,
|
|
||||||
},
|
|
||||||
|
|
||||||
stopChan: make(chan bool),
|
stopChan: make(chan bool),
|
||||||
|
|
||||||
|
@ -339,6 +334,10 @@ func NewHub(config *goconf.ConfigFile, nats NatsClient, r *mux.Router, version s
|
||||||
geoip: geoip,
|
geoip: geoip,
|
||||||
geoipOverrides: geoipOverrides,
|
geoipOverrides: geoipOverrides,
|
||||||
}
|
}
|
||||||
|
hub.setWelcomeMessage(&ServerMessage{
|
||||||
|
Type: "welcome",
|
||||||
|
Welcome: NewWelcomeServerMessage(version, DefaultWelcomeFeatures...),
|
||||||
|
})
|
||||||
backend.hub = hub
|
backend.hub = hub
|
||||||
hub.upgrader.CheckOrigin = hub.checkOrigin
|
hub.upgrader.CheckOrigin = hub.checkOrigin
|
||||||
r.HandleFunc("/spreed", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/spreed", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -348,49 +347,31 @@ func NewHub(config *goconf.ConfigFile, nats NatsClient, r *mux.Router, version s
|
||||||
return hub, nil
|
return hub, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addFeature(msg *HelloServerMessageServer, feature string) {
|
func (h *Hub) setWelcomeMessage(msg *ServerMessage) {
|
||||||
var newFeatures []string
|
h.welcome.Store(msg)
|
||||||
added := false
|
|
||||||
for _, f := range msg.Features {
|
|
||||||
newFeatures = append(newFeatures, f)
|
|
||||||
if f == feature {
|
|
||||||
added = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !added {
|
|
||||||
newFeatures = append(newFeatures, feature)
|
|
||||||
}
|
|
||||||
msg.Features = newFeatures
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFeature(msg *HelloServerMessageServer, feature string) {
|
func (h *Hub) getWelcomeMessage() *ServerMessage {
|
||||||
var newFeatures []string
|
return h.welcome.Load().(*ServerMessage)
|
||||||
for _, f := range msg.Features {
|
|
||||||
if f != feature {
|
|
||||||
newFeatures = append(newFeatures, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg.Features = newFeatures
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) SetMcu(mcu Mcu) {
|
func (h *Hub) SetMcu(mcu Mcu) {
|
||||||
h.mcu = mcu
|
h.mcu = mcu
|
||||||
|
// Create copy of message so it can be updated concurrently.
|
||||||
|
welcome := *h.getWelcomeMessage()
|
||||||
if mcu == nil {
|
if mcu == nil {
|
||||||
removeFeature(h.info, ServerFeatureMcu)
|
h.info.RemoveFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp)
|
||||||
removeFeature(h.info, ServerFeatureSimulcast)
|
h.infoInternal.RemoveFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp)
|
||||||
removeFeature(h.info, ServerFeatureUpdateSdp)
|
|
||||||
removeFeature(h.infoInternal, ServerFeatureMcu)
|
welcome.Welcome.RemoveFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp)
|
||||||
removeFeature(h.infoInternal, ServerFeatureSimulcast)
|
|
||||||
removeFeature(h.infoInternal, ServerFeatureUpdateSdp)
|
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Using a timeout of %s for MCU requests", h.mcuTimeout)
|
log.Printf("Using a timeout of %s for MCU requests", h.mcuTimeout)
|
||||||
addFeature(h.info, ServerFeatureMcu)
|
h.info.AddFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp)
|
||||||
addFeature(h.info, ServerFeatureSimulcast)
|
h.infoInternal.AddFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp)
|
||||||
addFeature(h.info, ServerFeatureUpdateSdp)
|
|
||||||
addFeature(h.infoInternal, ServerFeatureMcu)
|
welcome.Welcome.AddFeature(ServerFeatureMcu, ServerFeatureSimulcast, ServerFeatureUpdateSdp)
|
||||||
addFeature(h.infoInternal, ServerFeatureSimulcast)
|
|
||||||
addFeature(h.infoInternal, ServerFeatureUpdateSdp)
|
|
||||||
}
|
}
|
||||||
|
h.setWelcomeMessage(&welcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) checkOrigin(r *http.Request) bool {
|
func (h *Hub) checkOrigin(r *http.Request) bool {
|
||||||
|
@ -398,7 +379,7 @@ func (h *Hub) checkOrigin(r *http.Request) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) GetServerInfo(session Session) *HelloServerMessageServer {
|
func (h *Hub) GetServerInfo(session Session) *WelcomeServerMessage {
|
||||||
if session.ClientType() == HelloClientTypeInternal {
|
if session.ClientType() == HelloClientTypeInternal {
|
||||||
return h.infoInternal
|
return h.infoInternal
|
||||||
}
|
}
|
||||||
|
@ -685,6 +666,11 @@ func (h *Hub) startExpectHello(client *Client) {
|
||||||
|
|
||||||
func (h *Hub) processNewClient(client *Client) {
|
func (h *Hub) processNewClient(client *Client) {
|
||||||
h.startExpectHello(client)
|
h.startExpectHello(client)
|
||||||
|
h.sendWelcome(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hub) sendWelcome(client *Client) {
|
||||||
|
client.SendMessage(h.getWelcomeMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) newSessionIdData(backend *Backend) *SessionIdData {
|
func (h *Hub) newSessionIdData(backend *Backend) *SessionIdData {
|
||||||
|
|
23
hub_test.go
23
hub_test.go
|
@ -473,6 +473,29 @@ func performHousekeeping(hub *Hub, now time.Time) *sync.WaitGroup {
|
||||||
return &wg
|
return &wg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInitialWelcome(t *testing.T) {
|
||||||
|
hub, _, _, server := CreateHubForTest(t)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client := NewTestClientContext(ctx, t, server, hub)
|
||||||
|
defer client.CloseWithBye()
|
||||||
|
|
||||||
|
msg, err := client.RunUntilMessage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Type != "welcome" {
|
||||||
|
t.Errorf("Expected \"welcome\" message, got %+v", msg)
|
||||||
|
} else if msg.Welcome.Version == "" {
|
||||||
|
t.Errorf("Expected welcome version, got %+v", msg)
|
||||||
|
} else if len(msg.Welcome.Features) == 0 {
|
||||||
|
t.Errorf("Expected welcome features, got %+v", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExpectClientHello(t *testing.T) {
|
func TestExpectClientHello(t *testing.T) {
|
||||||
hub, _, _, server := CreateHubForTest(t)
|
hub, _, _, server := CreateHubForTest(t)
|
||||||
|
|
||||||
|
|
|
@ -591,7 +591,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) {
|
||||||
Hello: &signaling.HelloProxyServerMessage{
|
Hello: &signaling.HelloProxyServerMessage{
|
||||||
Version: signaling.HelloVersion,
|
Version: signaling.HelloVersion,
|
||||||
SessionId: session.PublicId(),
|
SessionId: session.PublicId(),
|
||||||
Server: &signaling.HelloServerMessageServer{
|
Server: &signaling.WelcomeServerMessage{
|
||||||
Version: s.version,
|
Version: s.version,
|
||||||
Country: s.country,
|
Country: s.country,
|
||||||
},
|
},
|
||||||
|
|
|
@ -190,9 +190,9 @@ type TestClient struct {
|
||||||
publicId string
|
publicId string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestClient(t *testing.T, server *httptest.Server, hub *Hub) *TestClient {
|
func NewTestClientContext(ctx context.Context, t *testing.T, server *httptest.Server, hub *Hub) *TestClient {
|
||||||
// Reference "hub" to prevent compiler error.
|
// Reference "hub" to prevent compiler error.
|
||||||
conn, _, err := websocket.DefaultDialer.Dial(getWebsocketUrl(server.URL), nil)
|
conn, _, err := websocket.DefaultDialer.DialContext(ctx, getWebsocketUrl(server.URL), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -228,6 +228,22 @@ func NewTestClient(t *testing.T, server *httptest.Server, hub *Hub) *TestClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewTestClient(t *testing.T, server *httptest.Server, hub *Hub) *TestClient {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client := NewTestClientContext(ctx, t, server, hub)
|
||||||
|
msg, err := client.RunUntilMessage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Type != "welcome" {
|
||||||
|
t.Errorf("Expected welcome message, got %+v", msg)
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
func (c *TestClient) CloseWithBye() {
|
func (c *TestClient) CloseWithBye() {
|
||||||
c.SendBye() // nolint
|
c.SendBye() // nolint
|
||||||
c.Close()
|
c.Close()
|
||||||
|
|
Loading…
Reference in a new issue