diff --git a/api_signaling.go b/api_signaling.go index 4bb15159..84851f73 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -41,6 +41,9 @@ const ( // Version 2.0 validates auth params encoded as JWT. HelloVersionV2 = "2.0" + + ActorTypeUsers = "users" + ActorTypeFederatedUsers = "federated_users" ) var ( @@ -48,6 +51,21 @@ var ( ErrInvalidSdp = NewError("invalid_sdp", "Payload does not contain a valid SDP.") ) +func makePtr[T any](v T) *T { + return &v +} + +func getStringMapEntry[T any](m map[string]interface{}, key string) (s T, ok bool) { + var defaultValue T + v, found := m[key] + if !found { + return defaultValue, false + } + + s, ok = v.(T) + return +} + // ClientMessage is a message that is sent from a client to the server. type ClientMessage struct { json.Marshaler @@ -121,7 +139,7 @@ func (m *ClientMessage) CheckValid() error { return nil } -func (m *ClientMessage) String() string { +func (m ClientMessage) String() string { data, err := json.Marshal(m) if err != nil { return fmt.Sprintf("Could not serialize %#v: %s", m, err) @@ -311,9 +329,21 @@ func (m *WelcomeServerMessage) RemoveFeature(feature ...string) { m.Features = newFeatures } +func (m *WelcomeServerMessage) HasFeature(feature string) bool { + for _, f := range m.Features { + f = strings.TrimSpace(f) + if f == feature { + return true + } + } + + return false +} + const ( - HelloClientTypeClient = "client" - HelloClientTypeInternal = "internal" + HelloClientTypeClient = "client" + HelloClientTypeInternal = "internal" + HelloClientTypeFederation = "federation" HelloClientTypeVirtual = "virtual" ) @@ -363,12 +393,53 @@ func (p *HelloV2AuthParams) CheckValid() error { return nil } +type AuthTokenClaims interface { + TokenSubject() string + TokenUserData() json.RawMessage + + VerifyIssuedAt(cmp time.Time, req bool) bool + VerifyExpiresAt(cmp time.Time, req bool) bool +} + type HelloV2TokenClaims struct { jwt.RegisteredClaims UserData json.RawMessage `json:"userdata,omitempty"` } +func (c *HelloV2TokenClaims) TokenSubject() string { + return c.Subject +} + +func (c *HelloV2TokenClaims) TokenUserData() json.RawMessage { + return c.UserData +} + +type FederationAuthParams struct { + Token string `json:"token"` +} + +func (p *FederationAuthParams) CheckValid() error { + if p.Token == "" { + return fmt.Errorf("token missing") + } + return nil +} + +type FederationTokenClaims struct { + jwt.RegisteredClaims + + UserData json.RawMessage `json:"userdata,omitempty"` +} + +func (c *FederationTokenClaims) TokenSubject() string { + return c.Subject +} + +func (c *FederationTokenClaims) TokenUserData() json.RawMessage { + return c.UserData +} + type HelloClientMessageAuth struct { // The client type that is connecting. Leave empty to use the default // "HelloClientTypeClient" @@ -379,8 +450,9 @@ type HelloClientMessageAuth struct { Url string `json:"url"` parsedUrl *url.URL - internalParams ClientTypeInternalAuthParams - helloV2Params HelloV2AuthParams + internalParams ClientTypeInternalAuthParams + helloV2Params HelloV2AuthParams + federationParams FederationAuthParams } // Type "hello" @@ -409,6 +481,8 @@ func (m *HelloClientMessage) CheckValid() error { } switch m.Auth.Type { case HelloClientTypeClient: + fallthrough + case HelloClientTypeFederation: if m.Auth.Url == "" { return fmt.Errorf("url missing") } else if u, err := url.ParseRequestURI(m.Auth.Url); err != nil { @@ -425,10 +499,19 @@ func (m *HelloClientMessage) CheckValid() error { case HelloVersionV1: // No additional validation necessary. case HelloVersionV2: - if err := json.Unmarshal(m.Auth.Params, &m.Auth.helloV2Params); err != nil { - return err - } else if err := m.Auth.helloV2Params.CheckValid(); err != nil { - return err + switch m.Auth.Type { + case HelloClientTypeClient: + if err := json.Unmarshal(m.Auth.Params, &m.Auth.helloV2Params); err != nil { + return err + } else if err := m.Auth.helloV2Params.CheckValid(); err != nil { + return err + } + case HelloClientTypeFederation: + if err := json.Unmarshal(m.Auth.Params, &m.Auth.federationParams); err != nil { + return err + } else if err := m.Auth.federationParams.CheckValid(); err != nil { + return err + } } } case HelloClientTypeInternal: @@ -456,6 +539,7 @@ const ( ServerFeatureHelloV2 = "hello-v2" ServerFeatureSwitchTo = "switchto" ServerFeatureDialout = "dialout" + ServerFeatureFederation = "federation" // Features to send to internal clients only. ServerFeatureInternalVirtualSessions = "virtual-sessions" @@ -474,6 +558,7 @@ var ( ServerFeatureHelloV2, ServerFeatureSwitchTo, ServerFeatureDialout, + ServerFeatureFederation, } DefaultFeaturesInternal = []string{ ServerFeatureInternalVirtualSessions, @@ -483,6 +568,7 @@ var ( ServerFeatureHelloV2, ServerFeatureSwitchTo, ServerFeatureDialout, + ServerFeatureFederation, } DefaultWelcomeFeatures = []string{ ServerFeatureAudioVideoPermissions, @@ -493,6 +579,7 @@ var ( ServerFeatureHelloV2, ServerFeatureSwitchTo, ServerFeatureDialout, + ServerFeatureFederation, } ) @@ -526,10 +613,56 @@ type ByeServerMessage struct { type RoomClientMessage struct { RoomId string `json:"roomid"` SessionId string `json:"sessionid,omitempty"` + + Federation *RoomFederationMessage `json:"federation,omitempty"` } func (m *RoomClientMessage) CheckValid() error { // No additional validation required. + if m.Federation != nil { + if err := m.Federation.CheckValid(); err != nil { + return err + } + } + + return nil +} + +type RoomFederationMessage struct { + SignalingUrl string `json:"signaling"` + parsedSignalingUrl *url.URL + + NextcloudUrl string `json:"url"` + parsedNextcloudUrl *url.URL + + RoomId string `json:"roomid,omitempty"` + Token string `json:"token"` +} + +func (m *RoomFederationMessage) CheckValid() error { + if m.SignalingUrl == "" { + return errors.New("signaling url missing") + } + + if m.SignalingUrl[len(m.SignalingUrl)-1] != '/' { + m.SignalingUrl += "/" + } + if u, err := url.Parse(m.SignalingUrl); err != nil { + return fmt.Errorf("invalid signaling url: %w", err) + } else { + m.parsedSignalingUrl = u + } + if m.NextcloudUrl == "" { + return errors.New("nextcloud url missing") + } else if u, err := url.Parse(m.NextcloudUrl); err != nil { + return fmt.Errorf("invalid nextcloud url: %w", err) + } else { + m.parsedNextcloudUrl = u + } + if m.Token == "" { + return errors.New("token missing") + } + return nil } @@ -909,6 +1042,7 @@ type EventServerMessage struct { Leave []string `json:"leave,omitempty"` Change []*EventServerMessageSessionEntry `json:"change,omitempty"` SwitchTo *EventServerMessageSwitchTo `json:"switchto,omitempty"` + Resumed *bool `json:"resumed,omitempty"` // Used for target "roomlist" / "participants" Invite *RoomEventServerMessage `json:"invite,omitempty"` @@ -933,6 +1067,7 @@ type EventServerMessageSessionEntry struct { UserId string `json:"userid"` User json.RawMessage `json:"user,omitempty"` RoomSessionId string `json:"roomsessionid,omitempty"` + Federated bool `json:"federated,omitempty"` } func (e *EventServerMessageSessionEntry) Clone() *EventServerMessageSessionEntry { @@ -941,6 +1076,7 @@ func (e *EventServerMessageSessionEntry) Clone() *EventServerMessageSessionEntry UserId: e.UserId, User: e.User, RoomSessionId: e.RoomSessionId, + Federated: e.Federated, } } diff --git a/api_signaling_easyjson.go b/api_signaling_easyjson.go index 5b43864c..773039fc 100644 --- a/api_signaling_easyjson.go +++ b/api_signaling_easyjson.go @@ -893,7 +893,94 @@ func (v *RoomFlagsServerMessage) UnmarshalJSON(data []byte) error { func (v *RoomFlagsServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling6(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlexer.Lexer, out *RoomEventServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlexer.Lexer, out *RoomFederationMessage) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "signaling": + out.SignalingUrl = string(in.String()) + case "url": + out.NextcloudUrl = string(in.String()) + case "roomid": + out.RoomId = string(in.String()) + case "token": + out.Token = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwriter.Writer, in RoomFederationMessage) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"signaling\":" + out.RawString(prefix[1:]) + out.String(string(in.SignalingUrl)) + } + { + const prefix string = ",\"url\":" + out.RawString(prefix) + out.String(string(in.NextcloudUrl)) + } + if in.RoomId != "" { + const prefix string = ",\"roomid\":" + out.RawString(prefix) + out.String(string(in.RoomId)) + } + { + const prefix string = ",\"token\":" + out.RawString(prefix) + out.String(string(in.Token)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v RoomFederationMessage) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v RoomFederationMessage) MarshalEasyJSON(w *jwriter.Writer) { + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *RoomFederationMessage) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *RoomFederationMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(l, v) +} +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlexer.Lexer, out *RoomEventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1028,7 +1115,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwriter.Writer, in RoomEventServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwriter.Writer, in RoomEventServerMessage) { out.RawByte('{') first := true _ = first @@ -1130,27 +1217,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(out *jwr // MarshalJSON supports json.Marshaler interface func (v RoomEventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling7(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling7(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlexer.Lexer, out *RoomEventMessageDataChat) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlexer.Lexer, out *RoomEventMessageDataChat) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1213,7 +1300,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwriter.Writer, in RoomEventMessageDataChat) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwriter.Writer, in RoomEventMessageDataChat) { out.RawByte('{') first := true _ = first @@ -1251,27 +1338,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(out *jwr // MarshalJSON supports json.Marshaler interface func (v RoomEventMessageDataChat) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventMessageDataChat) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling8(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventMessageDataChat) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventMessageDataChat) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling8(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlexer.Lexer, out *RoomEventMessageData) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *RoomEventMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1312,7 +1399,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(in *jlex in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwriter.Writer, in RoomEventMessageData) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in RoomEventMessageData) { out.RawByte('{') first := true _ = first @@ -1332,27 +1419,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(out *jwr // MarshalJSON supports json.Marshaler interface func (v RoomEventMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling9(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling9(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jlexer.Lexer, out *RoomEventMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *RoomEventMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1387,7 +1474,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jwriter.Writer, in RoomEventMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in RoomEventMessage) { out.RawByte('{') first := true _ = first @@ -1407,27 +1494,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(out *jw // MarshalJSON supports json.Marshaler interface func (v RoomEventMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomEventMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling10(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomEventMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomEventMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling10(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jlexer.Lexer, out *RoomErrorDetails) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *RoomErrorDetails) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1466,7 +1553,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jwriter.Writer, in RoomErrorDetails) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in RoomErrorDetails) { out.RawByte('{') first := true _ = first @@ -1485,27 +1572,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(out *jw // MarshalJSON supports json.Marshaler interface func (v RoomErrorDetails) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomErrorDetails) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling11(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomErrorDetails) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomErrorDetails) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling11(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jlexer.Lexer, out *RoomDisinviteEventServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *RoomDisinviteEventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1642,7 +1729,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jwriter.Writer, in RoomDisinviteEventServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in RoomDisinviteEventServerMessage) { out.RawByte('{') first := true _ = first @@ -1749,27 +1836,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(out *jw // MarshalJSON supports json.Marshaler interface func (v RoomDisinviteEventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomDisinviteEventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling12(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomDisinviteEventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomDisinviteEventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling12(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jlexer.Lexer, out *RoomClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *RoomClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1792,6 +1879,16 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle out.RoomId = string(in.String()) case "sessionid": out.SessionId = string(in.String()) + case "federation": + if in.IsNull() { + in.Skip() + out.Federation = nil + } else { + if out.Federation == nil { + out.Federation = new(RoomFederationMessage) + } + (*out.Federation).UnmarshalEasyJSON(in) + } default: in.SkipRecursive() } @@ -1802,7 +1899,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jwriter.Writer, in RoomClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in RoomClientMessage) { out.RawByte('{') first := true _ = first @@ -1816,33 +1913,38 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(out *jw out.RawString(prefix) out.String(string(in.SessionId)) } + if in.Federation != nil { + const prefix string = ",\"federation\":" + out.RawString(prefix) + (*in.Federation).MarshalEasyJSON(out) + } out.RawByte('}') } // MarshalJSON supports json.Marshaler interface func (v RoomClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RoomClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling13(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RoomClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RoomClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling13(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jlexer.Lexer, out *RemoveSessionInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *RemoveSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1877,7 +1979,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jwriter.Writer, in RemoveSessionInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in RemoveSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -1908,27 +2010,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(out *jw // MarshalJSON supports json.Marshaler interface func (v RemoveSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v RemoveSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling14(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *RemoveSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *RemoveSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling14(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jlexer.Lexer, out *MessageServerMessageSender) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *MessageServerMessageSender) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1963,7 +2065,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jwriter.Writer, in MessageServerMessageSender) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in MessageServerMessageSender) { out.RawByte('{') first := true _ = first @@ -1988,27 +2090,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageServerMessageSender) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessageSender) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling15(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessageSender) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessageSender) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling15(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jlexer.Lexer, out *MessageServerMessageDataChat) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *MessageServerMessageDataChat) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2039,7 +2141,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jwriter.Writer, in MessageServerMessageDataChat) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in MessageServerMessageDataChat) { out.RawByte('{') first := true _ = first @@ -2054,27 +2156,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageServerMessageDataChat) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessageDataChat) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling16(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessageDataChat) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessageDataChat) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling16(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jlexer.Lexer, out *MessageServerMessageData) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *MessageServerMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2115,7 +2217,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jwriter.Writer, in MessageServerMessageData) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in MessageServerMessageData) { out.RawByte('{') first := true _ = first @@ -2135,27 +2237,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageServerMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling17(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling17(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jlexer.Lexer, out *MessageServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *MessageServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2208,7 +2310,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jwriter.Writer, in MessageServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in MessageServerMessage) { out.RawByte('{') first := true _ = first @@ -2237,27 +2339,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling18(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling18(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jlexer.Lexer, out *MessageClientMessageRecipient) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *MessageClientMessageRecipient) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2292,7 +2394,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jwriter.Writer, in MessageClientMessageRecipient) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in MessageClientMessageRecipient) { out.RawByte('{') first := true _ = first @@ -2317,27 +2419,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageClientMessageRecipient) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessageRecipient) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling19(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessageRecipient) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessageRecipient) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling19(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jlexer.Lexer, out *MessageClientMessageData) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *MessageClientMessageData) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2396,7 +2498,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jwriter.Writer, in MessageClientMessageData) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in MessageClientMessageData) { out.RawByte('{') first := true _ = first @@ -2453,27 +2555,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageClientMessageData) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessageData) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling20(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessageData) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessageData) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling20(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jlexer.Lexer, out *MessageClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *MessageClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2508,7 +2610,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jwriter.Writer, in MessageClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in MessageClientMessage) { out.RawByte('{') first := true _ = first @@ -2528,27 +2630,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(out *jw // MarshalJSON supports json.Marshaler interface func (v MessageClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v MessageClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling21(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *MessageClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *MessageClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling21(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jlexer.Lexer, out *InternalServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *InternalServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2589,7 +2691,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jwriter.Writer, in InternalServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in InternalServerMessage) { out.RawByte('{') first := true _ = first @@ -2609,27 +2711,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(out *jw // MarshalJSON supports json.Marshaler interface func (v InternalServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling22(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling22(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jlexer.Lexer, out *InternalServerDialoutRequest) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *InternalServerDialoutRequest) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2672,7 +2774,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jwriter.Writer, in InternalServerDialoutRequest) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in InternalServerDialoutRequest) { out.RawByte('{') first := true _ = first @@ -2701,27 +2803,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(out *jw // MarshalJSON supports json.Marshaler interface func (v InternalServerDialoutRequest) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalServerDialoutRequest) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling23(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalServerDialoutRequest) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalServerDialoutRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling23(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jlexer.Lexer, out *InternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *InternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2802,7 +2904,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jwriter.Writer, in InternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in InternalClientMessage) { out.RawByte('{') first := true _ = first @@ -2842,27 +2944,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(out *jw // MarshalJSON supports json.Marshaler interface func (v InternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling24(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling24(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jlexer.Lexer, out *InCallInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *InCallInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -2893,7 +2995,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jwriter.Writer, in InCallInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in InCallInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -2908,27 +3010,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(out *jw // MarshalJSON supports json.Marshaler interface func (v InCallInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v InCallInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling25(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *InCallInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *InCallInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling25(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jlexer.Lexer, out *HelloV2TokenClaims) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *HelloV2TokenClaims) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3007,7 +3109,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jwriter.Writer, in HelloV2TokenClaims) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in HelloV2TokenClaims) { out.RawByte('{') first := true _ = first @@ -3093,27 +3195,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloV2TokenClaims) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloV2TokenClaims) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling26(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloV2TokenClaims) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloV2TokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling26(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jlexer.Lexer, out *HelloV2AuthParams) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *HelloV2AuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3144,7 +3246,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jwriter.Writer, in HelloV2AuthParams) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in HelloV2AuthParams) { out.RawByte('{') first := true _ = first @@ -3159,27 +3261,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloV2AuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloV2AuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling27(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloV2AuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloV2AuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling27(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jlexer.Lexer, out *HelloServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *HelloServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3226,7 +3328,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jwriter.Writer, in HelloServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in HelloServerMessage) { out.RawByte('{') first := true _ = first @@ -3261,27 +3363,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling28(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling28(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jlexer.Lexer, out *HelloClientMessageAuth) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *HelloClientMessageAuth) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3318,7 +3420,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jwriter.Writer, in HelloClientMessageAuth) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in HelloClientMessageAuth) { out.RawByte('{') first := true _ = first @@ -3349,27 +3451,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloClientMessageAuth) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloClientMessageAuth) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling29(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloClientMessageAuth) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloClientMessageAuth) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling29(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jlexer.Lexer, out *HelloClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *HelloClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3435,7 +3537,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jwriter.Writer, in HelloClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in HelloClientMessage) { out.RawByte('{') first := true _ = first @@ -3474,27 +3576,278 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(out *jw // MarshalJSON supports json.Marshaler interface func (v HelloClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v HelloClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling30(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *HelloClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *HelloClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling30(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) +} +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *FederationTokenClaims) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "userdata": + if data := in.Raw(); in.Ok() { + in.AddError((out.UserData).UnmarshalJSON(data)) + } + case "iss": + out.Issuer = string(in.String()) + case "sub": + out.Subject = string(in.String()) + case "aud": + if data := in.Raw(); in.Ok() { + in.AddError((out.Audience).UnmarshalJSON(data)) + } + case "exp": + if in.IsNull() { + in.Skip() + out.ExpiresAt = nil + } else { + if out.ExpiresAt == nil { + out.ExpiresAt = new(_v4.NumericDate) + } + if data := in.Raw(); in.Ok() { + in.AddError((*out.ExpiresAt).UnmarshalJSON(data)) + } + } + case "nbf": + if in.IsNull() { + in.Skip() + out.NotBefore = nil + } else { + if out.NotBefore == nil { + out.NotBefore = new(_v4.NumericDate) + } + if data := in.Raw(); in.Ok() { + in.AddError((*out.NotBefore).UnmarshalJSON(data)) + } + } + case "iat": + if in.IsNull() { + in.Skip() + out.IssuedAt = nil + } else { + if out.IssuedAt == nil { + out.IssuedAt = new(_v4.NumericDate) + } + if data := in.Raw(); in.Ok() { + in.AddError((*out.IssuedAt).UnmarshalJSON(data)) + } + } + case "jti": + out.ID = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in FederationTokenClaims) { + out.RawByte('{') + first := true + _ = first + if len(in.UserData) != 0 { + const prefix string = ",\"userdata\":" + first = false + out.RawString(prefix[1:]) + out.Raw((in.UserData).MarshalJSON()) + } + if in.Issuer != "" { + const prefix string = ",\"iss\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Issuer)) + } + if in.Subject != "" { + const prefix string = ",\"sub\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.Subject)) + } + if len(in.Audience) != 0 { + const prefix string = ",\"aud\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Raw((in.Audience).MarshalJSON()) + } + if in.ExpiresAt != nil { + const prefix string = ",\"exp\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Raw((*in.ExpiresAt).MarshalJSON()) + } + if in.NotBefore != nil { + const prefix string = ",\"nbf\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Raw((*in.NotBefore).MarshalJSON()) + } + if in.IssuedAt != nil { + const prefix string = ",\"iat\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Raw((*in.IssuedAt).MarshalJSON()) + } + if in.ID != "" { + const prefix string = ",\"jti\":" + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(string(in.ID)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v FederationTokenClaims) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v FederationTokenClaims) MarshalEasyJSON(w *jwriter.Writer) { + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *FederationTokenClaims) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *FederationTokenClaims) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) +} +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *FederationAuthParams) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "token": + out.Token = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in FederationAuthParams) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"token\":" + out.RawString(prefix[1:]) + out.String(string(in.Token)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v FederationAuthParams) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v FederationAuthParams) MarshalEasyJSON(w *jwriter.Writer) { + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *FederationAuthParams) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *FederationAuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jlexer.Lexer, out *EventServerMessageSwitchTo) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *EventServerMessageSwitchTo) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3529,7 +3882,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jwriter.Writer, in EventServerMessageSwitchTo) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in EventServerMessageSwitchTo) { out.RawByte('{') first := true _ = first @@ -3549,27 +3902,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessageSwitchTo) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessageSwitchTo) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling31(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessageSwitchTo) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessageSwitchTo) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling31(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jlexer.Lexer, out *EventServerMessageSessionEntry) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *EventServerMessageSessionEntry) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3598,6 +3951,8 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle } case "roomsessionid": out.RoomSessionId = string(in.String()) + case "federated": + out.Federated = bool(in.Bool()) default: in.SkipRecursive() } @@ -3608,7 +3963,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jwriter.Writer, in EventServerMessageSessionEntry) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in EventServerMessageSessionEntry) { out.RawByte('{') first := true _ = first @@ -3632,33 +3987,38 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(out *jw out.RawString(prefix) out.String(string(in.RoomSessionId)) } + if in.Federated { + const prefix string = ",\"federated\":" + out.RawString(prefix) + out.Bool(bool(in.Federated)) + } out.RawByte('}') } // MarshalJSON supports json.Marshaler interface func (v EventServerMessageSessionEntry) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessageSessionEntry) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling32(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessageSessionEntry) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessageSessionEntry) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling32(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jlexer.Lexer, out *EventServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *EventServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3776,6 +4136,16 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle } (*out.SwitchTo).UnmarshalEasyJSON(in) } + case "resumed": + if in.IsNull() { + in.Skip() + out.Resumed = nil + } else { + if out.Resumed == nil { + out.Resumed = new(bool) + } + *out.Resumed = bool(in.Bool()) + } case "invite": if in.IsNull() { in.Skip() @@ -3836,7 +4206,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jwriter.Writer, in EventServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in EventServerMessage) { out.RawByte('{') first := true _ = first @@ -3905,6 +4275,11 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jw out.RawString(prefix) (*in.SwitchTo).MarshalEasyJSON(out) } + if in.Resumed != nil { + const prefix string = ",\"resumed\":" + out.RawString(prefix) + out.Bool(bool(*in.Resumed)) + } if in.Invite != nil { const prefix string = ",\"invite\":" out.RawString(prefix) @@ -3936,27 +4311,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(out *jw // MarshalJSON supports json.Marshaler interface func (v EventServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v EventServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling33(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *EventServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *EventServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling33(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jlexer.Lexer, out *Error) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jlexer.Lexer, out *Error) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -3993,7 +4368,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jwriter.Writer, in Error) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jwriter.Writer, in Error) { out.RawByte('{') first := true _ = first @@ -4018,27 +4393,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(out *jw // MarshalJSON supports json.Marshaler interface func (v Error) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v Error) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling34(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *Error) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *Error) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling34(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jlexer.Lexer, out *DialoutStatusInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jlexer.Lexer, out *DialoutStatusInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4077,7 +4452,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jwriter.Writer, in DialoutStatusInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jwriter.Writer, in DialoutStatusInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -4112,27 +4487,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(out *jw // MarshalJSON supports json.Marshaler interface func (v DialoutStatusInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v DialoutStatusInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling35(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *DialoutStatusInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *DialoutStatusInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling35(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jlexer.Lexer, out *DialoutInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jlexer.Lexer, out *DialoutInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4185,7 +4560,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jwriter.Writer, in DialoutInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jwriter.Writer, in DialoutInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -4215,27 +4590,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(out *jw // MarshalJSON supports json.Marshaler interface func (v DialoutInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v DialoutInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling36(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *DialoutInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *DialoutInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling36(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jlexer.Lexer, out *ControlServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jlexer.Lexer, out *ControlServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4288,7 +4663,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jwriter.Writer, in ControlServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jwriter.Writer, in ControlServerMessage) { out.RawByte('{') first := true _ = first @@ -4317,27 +4692,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(out *jw // MarshalJSON supports json.Marshaler interface func (v ControlServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ControlServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling37(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ControlServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ControlServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling37(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jlexer.Lexer, out *ControlClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jlexer.Lexer, out *ControlClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4372,7 +4747,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jwriter.Writer, in ControlClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jwriter.Writer, in ControlClientMessage) { out.RawByte('{') first := true _ = first @@ -4392,27 +4767,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(out *jw // MarshalJSON supports json.Marshaler interface func (v ControlClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ControlClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling38(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ControlClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ControlClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling38(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jlexer.Lexer, out *CommonSessionInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jlexer.Lexer, out *CommonSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4445,7 +4820,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jwriter.Writer, in CommonSessionInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jwriter.Writer, in CommonSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -4465,27 +4840,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(out *jw // MarshalJSON supports json.Marshaler interface func (v CommonSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v CommonSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling39(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *CommonSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *CommonSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling39(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jlexer.Lexer, out *ClientTypeInternalAuthParams) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jlexer.Lexer, out *ClientTypeInternalAuthParams) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4520,7 +4895,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jwriter.Writer, in ClientTypeInternalAuthParams) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jwriter.Writer, in ClientTypeInternalAuthParams) { out.RawByte('{') first := true _ = first @@ -4545,27 +4920,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(out *jw // MarshalJSON supports json.Marshaler interface func (v ClientTypeInternalAuthParams) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientTypeInternalAuthParams) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling40(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientTypeInternalAuthParams) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientTypeInternalAuthParams) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling40(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jlexer.Lexer, out *ClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jlexer.Lexer, out *ClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4668,7 +5043,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jwriter.Writer, in ClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jwriter.Writer, in ClientMessage) { out.RawByte('{') first := true _ = first @@ -4729,27 +5104,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(out *jw // MarshalJSON supports json.Marshaler interface func (v ClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling41(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling41(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jlexer.Lexer, out *ByeServerMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jlexer.Lexer, out *ByeServerMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4780,7 +5155,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jwriter.Writer, in ByeServerMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jwriter.Writer, in ByeServerMessage) { out.RawByte('{') first := true _ = first @@ -4795,27 +5170,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(out *jw // MarshalJSON supports json.Marshaler interface func (v ByeServerMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeServerMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling42(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeServerMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeServerMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling42(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jlexer.Lexer, out *ByeClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jlexer.Lexer, out *ByeClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4844,7 +5219,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jwriter.Writer, in ByeClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jwriter.Writer, in ByeClientMessage) { out.RawByte('{') first := true _ = first @@ -4854,27 +5229,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(out *jw // MarshalJSON supports json.Marshaler interface func (v ByeClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ByeClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling43(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ByeClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ByeClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling43(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jlexer.Lexer, out *AnswerOfferMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(in *jlexer.Lexer, out *AnswerOfferMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -4935,7 +5310,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jwriter.Writer, in AnswerOfferMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(out *jwriter.Writer, in AnswerOfferMessage) { out.RawByte('{') first := true _ = first @@ -4997,27 +5372,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(out *jw // MarshalJSON supports json.Marshaler interface func (v AnswerOfferMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AnswerOfferMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling44(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling47(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AnswerOfferMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AnswerOfferMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling44(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling47(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jlexer.Lexer, out *AddSessionOptions) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(in *jlexer.Lexer, out *AddSessionOptions) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5050,7 +5425,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jwriter.Writer, in AddSessionOptions) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(out *jwriter.Writer, in AddSessionOptions) { out.RawByte('{') first := true _ = first @@ -5076,27 +5451,27 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(out *jw // MarshalJSON supports json.Marshaler interface func (v AddSessionOptions) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddSessionOptions) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling45(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling48(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddSessionOptions) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddSessionOptions) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling45(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling48(l, v) } -func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jlexer.Lexer, out *AddSessionInternalClientMessage) { +func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(in *jlexer.Lexer, out *AddSessionInternalClientMessage) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -5157,7 +5532,7 @@ func easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(in *jle in.Consumed() } } -func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jwriter.Writer, in AddSessionInternalClientMessage) { +func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(out *jwriter.Writer, in AddSessionInternalClientMessage) { out.RawByte('{') first := true _ = first @@ -5228,23 +5603,23 @@ func easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(out *jw // MarshalJSON supports json.Marshaler interface func (v AddSessionInternalClientMessage) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(&w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v AddSessionInternalClientMessage) MarshalEasyJSON(w *jwriter.Writer) { - easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling46(w, v) + easyjson29f189fbEncodeGithubComStrukturagNextcloudSpreedSignaling49(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *AddSessionInternalClientMessage) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(&r, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *AddSessionInternalClientMessage) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling46(l, v) + easyjson29f189fbDecodeGithubComStrukturagNextcloudSpreedSignaling49(l, v) } diff --git a/capabilities.go b/capabilities.go index 433b15c4..45b7b546 100644 --- a/capabilities.go +++ b/capabilities.go @@ -43,6 +43,9 @@ const ( // Name of capability to enable the "v3" API for the signaling endpoint. FeatureSignalingV3Api = "signaling-v3" + // Name of capability that is set if the server supports Federation V2. + FeatureFederationV2 = "federation-v2" + // minCapabilitiesCacheDuration specifies the minimum duration to cache // capabilities. // This could overwrite the "max-age" from a "Cache-Control" header. diff --git a/client.go b/client.go index c7d1cc13..c9f1de09 100644 --- a/client.go +++ b/client.go @@ -365,7 +365,7 @@ func (c *Client) ReadPump() { messageType, reader, err := conn.NextReader() if err != nil { // Gorilla websocket hides the original net.Error, so also compare error messages - if errors.Is(err, net.ErrClosed) || strings.Contains(err.Error(), net.ErrClosed.Error()) { + if errors.Is(err, net.ErrClosed) || errors.Is(err, websocket.ErrCloseSent) || strings.Contains(err.Error(), net.ErrClosed.Error()) { break } else if _, ok := err.(*websocket.CloseError); !ok || websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure, diff --git a/clientsession.go b/clientsession.go index d4e8c40e..5e2fe4f5 100644 --- a/clientsession.go +++ b/clientsession.go @@ -72,6 +72,7 @@ type ClientSession struct { client HandlerClient room atomic.Pointer[Room] roomJoinTime atomic.Int64 + federation atomic.Pointer[FederationClient] roomSessionIdLock sync.RWMutex roomSessionId string @@ -321,7 +322,11 @@ func (s *ClientSession) UserData() json.RawMessage { func (s *ClientSession) SetRoom(room *Room) { s.room.Store(room) - if room != nil { + s.onRoomSet(room != nil) +} + +func (s *ClientSession) onRoomSet(hasRoom bool) { + if hasRoom { s.roomJoinTime.Store(time.Now().UnixNano()) } else { s.roomJoinTime.Store(0) @@ -332,6 +337,22 @@ func (s *ClientSession) SetRoom(room *Room) { s.seenJoinedEvents = nil } +func (s *ClientSession) GetFederationClient() *FederationClient { + return s.federation.Load() +} + +func (s *ClientSession) SetFederationClient(federation *FederationClient) { + s.mu.Lock() + defer s.mu.Unlock() + + s.doLeaveRoom(true) + s.onRoomSet(federation != nil) + + if prev := s.federation.Swap(federation); prev != nil && prev != federation { + prev.Close() + } +} + func (s *ClientSession) GetRoom() *Room { return s.room.Load() } @@ -374,6 +395,10 @@ func (s *ClientSession) closeAndWait(wait bool) { s.closeFunc() s.hub.removeSession(s) + if prev := s.federation.Swap(nil); prev != nil { + prev.Close() + } + s.mu.Lock() defer s.mu.Unlock() if s.userId != "" { @@ -467,9 +492,26 @@ func (s *ClientSession) LeaveCall() { } func (s *ClientSession) LeaveRoom(notify bool) *Room { + return s.LeaveRoomWithMessage(notify, nil) +} + +func (s *ClientSession) LeaveRoomWithMessage(notify bool, message *ClientMessage) *Room { + if prev := s.federation.Swap(nil); prev != nil { + // Session was connected to a federation room. + if err := prev.Leave(message); err != nil { + log.Printf("Error leaving room for session %s on federation client %s: %s", s.PublicId(), prev.URL(), err) + prev.Close() + } + return nil + } + s.mu.Lock() defer s.mu.Unlock() + return s.doLeaveRoom(notify) +} + +func (s *ClientSession) doLeaveRoom(notify bool) *Room { room := s.GetRoom() if room == nil { return nil diff --git a/docs/standalone-signaling-api-v1.md b/docs/standalone-signaling-api-v1.md index d05ae68d..6646bdc6 100644 --- a/docs/standalone-signaling-api-v1.md +++ b/docs/standalone-signaling-api-v1.md @@ -437,7 +437,8 @@ Message format (Client -> Server): - The client can ask about joining a room using this request. - The session id received from the PHP backend must be passed as `sessionid`. -- The `roomid` can be empty to leave the room. +- The `roomid` can be empty to leave the room the client is currently in + (local or federated). - A session can only be connected to one room, i.e. joining a room will leave the room currently in. @@ -524,6 +525,95 @@ user, the backend returns an error and the room request will be rejected. to the room. +## Join federated room + +If the features list contains the id `federation`, the signaling server supports +joining rooms on external signaling servers for Nextcloud instances not +configured in the local server. + +Message format (Client -> Server): + + { + "id": "unique-request-id", + "type": "room", + "room": { + "roomid": "the-local-room-id", + "sessionid": "the-nextcloud-session-id", + "federation": { + "signaling": "wss://remote.domain.invalid/path/to/signaling/", + "url": "https://remote.domain.invalid/path/to/nextcloud/", + "roomid": "the-remote-room-id", + "token": "hello-v2-auth-token-for-remote-signaling-server" + } + } + } + +- The remote room id is optional. If omitted, the local room id will be used. +- If a session joins a federated room, any local room will be left. + +Message format (Server -> Client): + + { + "id": "unique-request-id-from-request", + "type": "room", + "room": { + "roomid": "the-local-room-id", + "properties": { + ...additional room properties... + } + } + } + +- Sent to confirm a request from the client. + + +### Error codes + +- `federation_unsupported`: Federation is not supported by the target server. +- `federation_error`: Error while creating connection to target server + (additional information might be available in `details`). + +Also the error codes from joining a regular room could be returned. + + +### Events + +The signaling server tries to resume the internal proxy session if the +connection to the remote server gets interrupted. To notify clients about these +interruptions, two additional events may be sent from the server to the client: + +Connection was interrupted (Server -> Client): + + { + "type": "event", + "event": { + "target": "room", + "type": "federation_interrupted" + } + } + + +Connection was resumed (Server -> Client): + + { + "type": "event", + "event": { + "target": "room", + "type": "federation_resumed", + "resumed": true + } + } + +The `resumed` flag will be `true` if the existing internal session could be +resumed (i.e. the client stayed in the remote room), or `false` if a new +internal session was created. + +If a new internal session was created, the client will receive another `room` +event for the joined room and `join` events for the different participants in +the room. This should be handled the same as if the direct session could not +be resumed on reconnect. + + ## Leave room To leave a room, a [join room](#join-room) message must be sent with an empty @@ -560,6 +650,10 @@ Room event session object: } } +If a session is federated, an additional entry `"federated": true` will be +available. + + Message format (Server -> Client, user(s) left): { diff --git a/federation.go b/federation.go new file mode 100644 index 00000000..b93dacc3 --- /dev/null +++ b/federation.go @@ -0,0 +1,873 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2024 struktur AG + * + * @author Joachim Bauch + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package signaling + +import ( + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "log" + "net" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/gorilla/websocket" + easyjson "github.com/mailru/easyjson" +) + +const ( + initialFederationReconnectInterval = 100 * time.Millisecond + maxFederationReconnectInterval = 8 * time.Second +) + +var ( + ErrFederationNotSupported = NewError("federation_unsupported", "The target server does not support federation.") +) + +func isClosedError(err error) bool { + return errors.Is(err, net.ErrClosed) || + errors.Is(err, websocket.ErrCloseSent) || + // Gorilla websocket hides the original net.Error, so also compare error messages + strings.Contains(err.Error(), net.ErrClosed.Error()) +} + +func getCloudUrl(s string) string { + if strings.HasPrefix(s, "https://") { + s = s[8:] + } else { + s = strings.TrimPrefix(s, "http://") + } + if pos := strings.Index(s, "/ocs/v"); pos != -1 { + s = s[:pos] + } + return s +} + +type FederationClient struct { + hub *Hub + session *ClientSession + message atomic.Pointer[ClientMessage] + + roomId atomic.Value + remoteRoomId atomic.Value + changeRoomId atomic.Bool + federation atomic.Pointer[RoomFederationMessage] + + mu sync.Mutex + dialer *websocket.Dialer + url string + conn *websocket.Conn + closer *Closer + reconnectDelay time.Duration + reconnecting bool + reconnectFunc *time.Timer + + helloMu sync.Mutex + helloMsgId string + helloAuth *FederationAuthParams + resumeId string + hello atomic.Pointer[HelloServerMessage] + + pendingMessages []*ClientMessage + + closeOnLeave atomic.Bool +} + +func NewFederationClient(ctx context.Context, hub *Hub, session *ClientSession, message *ClientMessage) (*FederationClient, error) { + if message.Type != "room" || message.Room == nil || message.Room.Federation == nil { + return nil, fmt.Errorf("expected federation room message, got %+v", message) + } + + var dialer websocket.Dialer + if hub.skipFederationVerify { + dialer.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: true, + } + } + + room := message.Room + u := *room.Federation.parsedSignalingUrl + switch u.Scheme { + case "http": + u.Scheme = "ws" + case "https": + u.Scheme = "wss" + } + url := u.String() + "spreed" + + remoteRoomId := room.Federation.RoomId + if remoteRoomId == "" { + remoteRoomId = room.RoomId + } + + result := &FederationClient{ + hub: hub, + session: session, + + reconnectDelay: initialFederationReconnectInterval, + + dialer: &dialer, + url: url, + closer: NewCloser(), + } + result.roomId.Store(room.RoomId) + result.remoteRoomId.Store(remoteRoomId) + result.changeRoomId.Store(room.RoomId != remoteRoomId) + result.federation.Store(room.Federation) + result.message.Store(message) + + if err := result.connect(ctx); err != nil { + return nil, err + } + + go func() { + hub.writePumpActive.Add(1) + defer hub.writePumpActive.Add(-1) + + result.writePump() + }() + + return result, nil +} + +func (c *FederationClient) URL() string { + return c.federation.Load().parsedSignalingUrl.String() +} + +func (c *FederationClient) CanReuse(federation *RoomFederationMessage) bool { + fed := c.federation.Load() + return fed.NextcloudUrl == federation.NextcloudUrl && + fed.SignalingUrl == federation.SignalingUrl +} + +func (c *FederationClient) connect(ctx context.Context) error { + log.Printf("Creating federation connection to %s for %s", c.URL(), c.session.PublicId()) + conn, response, err := c.dialer.DialContext(ctx, c.url, nil) + if err != nil { + return err + } + + features := strings.Split(response.Header.Get("X-Spreed-Signaling-Features"), ",") + supportsFederation := false + for _, f := range features { + f = strings.TrimSpace(f) + if f == ServerFeatureFederation { + supportsFederation = true + break + } + } + if !supportsFederation { + if err := conn.Close(); err != nil { + log.Printf("Error closing federation connection to %s: %s", c.URL(), err) + } + + return ErrFederationNotSupported + } + + log.Printf("Federation connection established to %s for %s", c.URL(), c.session.PublicId()) + + c.mu.Lock() + defer c.mu.Unlock() + + if c.reconnectFunc != nil { + c.reconnectFunc.Stop() + c.reconnectFunc = nil + } + + c.conn = conn + + go func() { + c.hub.readPumpActive.Add(1) + defer c.hub.readPumpActive.Add(-1) + + c.readPump(conn) + }() + + return nil +} + +func (c *FederationClient) ChangeRoom(message *ClientMessage) error { + if message.Room == nil || message.Room.Federation == nil { + return fmt.Errorf("expected federation room message, got %+v", message) + } else if !c.CanReuse(message.Room.Federation) { + return fmt.Errorf("can't reuse federation client to join room in %+v", message) + } + + c.message.Swap(message) + return c.joinRoom() +} + +func (c *FederationClient) Leave(message *ClientMessage) error { + c.mu.Lock() + defer c.mu.Unlock() + + if message == nil { + message = &ClientMessage{ + Type: "room", + Room: &RoomClientMessage{ + RoomId: "", + }, + } + } + + if err := c.sendMessageLocked(message); err != nil && !errors.Is(err, websocket.ErrCloseSent) { + return err + } + + c.closeOnLeave.Store(true) + return nil +} + +func (c *FederationClient) Close() { + c.closer.Close() + + c.mu.Lock() + defer c.mu.Unlock() + + c.closeConnection(true) +} + +func (c *FederationClient) closeConnection(withBye bool) { + if c.conn == nil { + return + } + + if withBye { + if err := c.sendMessageLocked(&ClientMessage{ + Type: "bye", + }); err != nil && !isClosedError(err) { + log.Printf("Error sending bye on federation connection to %s: %s", c.URL(), err) + } + } + + closeMessage := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "") + deadline := time.Now().Add(writeWait) + if err := c.conn.WriteControl(websocket.CloseMessage, closeMessage, deadline); err != nil && !isClosedError(err) { + log.Printf("Error sending close message on federation connection to %s: %s", c.URL(), err) + } + + if err := c.conn.Close(); err != nil && !isClosedError(err) { + log.Printf("Error closing federation connection to %s: %s", c.URL(), err) + } + + c.conn = nil +} + +func (c *FederationClient) resetReconnect() { + c.mu.Lock() + defer c.mu.Unlock() + c.reconnectDelay = initialFederationReconnectInterval +} + +func (c *FederationClient) scheduleReconnect() { + c.mu.Lock() + defer c.mu.Unlock() + + c.scheduleReconnectLocked() +} + +func (c *FederationClient) scheduleReconnectLocked() { + c.reconnecting = true + if c.hello.Swap(nil) != nil { + c.session.SendMessage(&ServerMessage{ + Type: "event", + Event: &EventServerMessage{ + Target: "room", + Type: "federation_interrupted", + }, + }) + } + c.closeConnection(false) + + if c.reconnectFunc != nil { + c.reconnectFunc.Stop() + } + c.reconnectFunc = time.AfterFunc(c.reconnectDelay, c.reconnect) + c.reconnectDelay *= 2 + if c.reconnectDelay > maxFederationReconnectInterval { + c.reconnectDelay = maxFederationReconnectInterval + } +} + +func (c *FederationClient) reconnect() { + if c.closer.IsClosed() { + return + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.hub.federationTimeout)) + defer cancel() + + if err := c.connect(ctx); err != nil { + log.Printf("Error connecting to federation server %s for %s: %s", c.URL(), c.session.PublicId(), err) + c.scheduleReconnect() + return + } +} + +func (c *FederationClient) readPump(conn *websocket.Conn) { + conn.SetReadLimit(maxMessageSize) + conn.SetPongHandler(func(msg string) error { + now := time.Now() + conn.SetReadDeadline(now.Add(pongWait)) // nolint + return nil + }) + + for { + conn.SetReadDeadline(time.Now().Add(pongWait)) // nolint + msgType, data, err := conn.ReadMessage() + if err != nil { + if c.closer.IsClosed() && isClosedError(err) { + // Connection closed locally, no need to reconnect. + break + } + + if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { + log.Printf("Error reading from %s for %s: %s", c.URL(), c.session.PublicId(), err) + } + + c.scheduleReconnect() + break + } + + if msgType != websocket.TextMessage { + continue + } + + var msg ServerMessage + if err := json.Unmarshal(data, &msg); err != nil { + log.Printf("Error unmarshalling %s from %s: %s", string(data), c.URL(), err) + continue + } + + if c.hello.Load() == nil { + switch msg.Type { + case "welcome": + c.processWelcome(&msg) + default: + c.processHello(&msg) + } + continue + } + + c.processMessage(&msg) + } +} + +func (c *FederationClient) sendPing() { + c.mu.Lock() + defer c.mu.Unlock() + if c.conn == nil { + return + } + + now := time.Now().UnixNano() + msg := strconv.FormatInt(now, 10) + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint + if err := c.conn.WriteMessage(websocket.PingMessage, []byte(msg)); err != nil { + log.Printf("Could not send ping to federated client %s for %s: %v", c.URL(), c.session.PublicId(), err) + c.scheduleReconnectLocked() + } +} + +func (c *FederationClient) writePump() { + ticker := time.NewTicker(pingPeriod) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + c.sendPing() + case <-c.closer.C: + return + } + } +} + +func (c *FederationClient) closeWithError(err error) { + c.Close() + var e *Error + if !errors.As(err, &e) { + e = NewError("federation_error", err.Error()) + } + + var id string + if message := c.message.Swap(nil); message != nil { + id = message.Id + } + + c.session.SendMessage(&ServerMessage{ + Id: id, + Type: "error", + Error: e, + }) +} + +func (c *FederationClient) sendHello(auth *FederationAuthParams) error { + c.helloMu.Lock() + defer c.helloMu.Unlock() + + return c.sendHelloLocked(auth) +} + +func (c *FederationClient) sendHelloLocked(auth *FederationAuthParams) error { + c.helloMsgId = newRandomString(8) + + authData, err := json.Marshal(auth) + if err != nil { + return fmt.Errorf("Error marshalling hello auth message %+v for %s: %s", auth, c.session.PublicId(), err) + } + + c.helloAuth = auth + msg := &ClientMessage{ + Id: c.helloMsgId, + Type: "hello", + Hello: &HelloClientMessage{ + Version: HelloVersionV2, + }, + } + if resumeId := c.resumeId; resumeId != "" { + msg.Hello.ResumeId = resumeId + } else { + msg.Hello.Auth = &HelloClientMessageAuth{ + Type: HelloClientTypeFederation, + Url: c.federation.Load().NextcloudUrl, + Params: authData, + } + } + return c.SendMessage(msg) +} + +func (c *FederationClient) processWelcome(msg *ServerMessage) { + if !msg.Welcome.HasFeature(ServerFeatureFederation) { + c.closeWithError(ErrFederationNotSupported) + return + } + + federationParams := &FederationAuthParams{ + Token: c.federation.Load().Token, + } + if err := c.sendHello(federationParams); err != nil { + log.Printf("Error sending hello message to %s for %s: %s", c.URL(), c.session.PublicId(), err) + c.closeWithError(err) + } +} + +func (c *FederationClient) processHello(msg *ServerMessage) { + c.resetReconnect() + + c.helloMu.Lock() + defer c.helloMu.Unlock() + + if msg.Id != c.helloMsgId { + log.Printf("Received hello response %+v for unknown request, expected %s", msg, c.helloMsgId) + if err := c.sendHelloLocked(c.helloAuth); err != nil { + c.closeWithError(err) + } + return + } + + c.helloMsgId = "" + if msg.Type == "error" { + switch msg.Error.Code { + case "no_such_session": + // Resume failed (e.g. remote has restarted), try to connect new session + // which may fail if the auth token has expired in the meantime. + c.resumeId = "" + c.pendingMessages = nil + if err := c.sendHelloLocked(c.helloAuth); err != nil { + c.closeWithError(err) + } + default: + log.Printf("Received hello error from federated client for %s to %s: %+v", c.session.PublicId(), c.URL(), msg) + c.closeWithError(msg.Error) + } + return + } else if msg.Type != "hello" { + log.Printf("Received unknown hello response from federated client for %s to %s: %+v", c.session.PublicId(), c.URL(), msg) + if err := c.sendHelloLocked(c.helloAuth); err != nil { + c.closeWithError(err) + } + return + } + + c.hello.Store(msg.Hello) + if c.resumeId == "" { + c.resumeId = msg.Hello.ResumeId + if c.reconnecting { + c.session.SendMessage(&ServerMessage{ + Type: "event", + Event: &EventServerMessage{ + Target: "room", + Type: "federation_resumed", + Resumed: makePtr(false), + }, + }) + // Setting the federation client will reset any information on previously + // received "join" events. + c.session.SetFederationClient(c) + } + + if err := c.joinRoom(); err != nil { + c.closeWithError(err) + } + } else { + c.session.SendMessage(&ServerMessage{ + Type: "event", + Event: &EventServerMessage{ + Target: "room", + Type: "federation_resumed", + Resumed: makePtr(true), + }, + }) + + if count := len(c.pendingMessages); count > 0 { + messages := c.pendingMessages + c.pendingMessages = nil + + log.Printf("Sending %d pending messages to %s for %s", count, c.URL(), c.session.PublicId()) + + c.helloMu.Unlock() + defer c.helloMu.Lock() + + c.mu.Lock() + defer c.mu.Unlock() + for _, msg := range messages { + if err := c.sendMessageLocked(msg); err != nil { + log.Printf("Error sending pending message %+v on federation connection to %s: %s", msg, c.URL(), err) + break + } + } + } + } +} + +func (c *FederationClient) joinRoom() error { + message := c.message.Load() + if message == nil { + // Should not happen as the connection has been closed with an error already. + return ErrNotConnected + } + + room := message.Room + remoteRoomId := room.Federation.RoomId + if remoteRoomId == "" { + remoteRoomId = room.RoomId + } + + return c.SendMessage(&ClientMessage{ + Id: message.Id, + Type: "room", + Room: &RoomClientMessage{ + RoomId: remoteRoomId, + SessionId: room.SessionId, + }, + }) +} + +func (c *FederationClient) updateEventUsers(users []map[string]interface{}, localSessionId string, remoteSessionId string) { + localCloudUrl := "@" + getCloudUrl(c.session.BackendUrl()) + localCloudUrlLen := len(localCloudUrl) + remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl) + checkSessionId := true + for _, u := range users { + if actorType, found := getStringMapEntry[string](u, "actorType"); found { + if actorId, found := getStringMapEntry[string](u, "actorId"); found { + switch actorType { + case ActorTypeFederatedUsers: + if strings.HasSuffix(actorId, localCloudUrl) { + u["actorId"] = actorId[:len(actorId)-localCloudUrlLen] + u["actorType"] = ActorTypeUsers + } + case ActorTypeUsers: + u["actorId"] = actorId + remoteCloudUrl + u["actorType"] = ActorTypeFederatedUsers + } + } + } + + if checkSessionId { + key := "sessionId" + sid, found := getStringMapEntry[string](u, key) + if !found { + key := "sessionid" + sid, found = getStringMapEntry[string](u, key) + } + if found && sid == remoteSessionId { + u[key] = localSessionId + checkSessionId = false + } + } + } +} + +func (c *FederationClient) updateSessionRecipient(recipient *MessageClientMessageRecipient, localSessionId string, remoteSessionId string) { + if recipient != nil && recipient.Type == RecipientTypeSession && remoteSessionId != "" && recipient.SessionId == remoteSessionId { + recipient.SessionId = localSessionId + } +} + +func (c *FederationClient) updateSessionSender(sender *MessageServerMessageSender, localSessionId string, remoteSessionId string) { + if sender != nil && sender.Type == RecipientTypeSession && remoteSessionId != "" && sender.SessionId == remoteSessionId { + sender.SessionId = localSessionId + } +} + +func (c *FederationClient) processMessage(msg *ServerMessage) { + localSessionId := c.session.PublicId() + var remoteSessionId string + if hello := c.hello.Load(); hello != nil { + remoteSessionId = hello.SessionId + } + + remoteRoomId := c.remoteRoomId.Load().(string) + roomId := c.roomId.Load().(string) + + var doClose bool + switch msg.Type { + case "control": + c.updateSessionRecipient(msg.Control.Recipient, localSessionId, remoteSessionId) + c.updateSessionSender(msg.Control.Sender, localSessionId, remoteSessionId) + // Special handling for "forceMute" event. + if len(msg.Control.Data) > 0 && msg.Control.Data[0] == '{' { + var data map[string]interface{} + if err := json.Unmarshal(msg.Control.Data, &data); err == nil { + if action, found := data["action"]; found && action == "forceMute" { + if peerId, found := data["peerId"]; found && peerId == remoteSessionId { + data["peerId"] = localSessionId + if d, err := json.Marshal(data); err == nil { + msg.Control.Data = d + } + } + } + } + } + case "event": + switch msg.Event.Target { + case "participants": + switch msg.Event.Type { + case "update": + if c.changeRoomId.Load() && msg.Event.Update.RoomId == remoteRoomId { + msg.Event.Update.RoomId = roomId + } + if remoteSessionId != "" { + c.updateEventUsers(msg.Event.Update.Changed, localSessionId, remoteSessionId) + c.updateEventUsers(msg.Event.Update.Users, localSessionId, remoteSessionId) + } + case "flags": + if c.changeRoomId.Load() && msg.Event.Flags.RoomId == remoteRoomId { + msg.Event.Flags.RoomId = roomId + } + if remoteSessionId != "" && msg.Event.Flags.SessionId == remoteSessionId { + msg.Event.Flags.SessionId = localSessionId + } + case "message": + if c.changeRoomId.Load() && msg.Event.Message.RoomId == remoteRoomId { + msg.Event.Message.RoomId = roomId + } + } + case "room": + switch msg.Event.Type { + case "join": + if remoteSessionId != "" { + for _, j := range msg.Event.Join { + if j.SessionId == remoteSessionId { + j.SessionId = localSessionId + break + } + } + } + case "leave": + if remoteSessionId != "" { + for idx, j := range msg.Event.Leave { + if j == remoteSessionId { + msg.Event.Leave[idx] = localSessionId + if c.closeOnLeave.Load() { + doClose = true + } + break + } + } + } + case "message": + if c.changeRoomId.Load() && msg.Event.Message.RoomId == remoteRoomId { + msg.Event.Message.RoomId = roomId + } + } + case "roomlist": + switch msg.Event.Type { + case "invite": + if c.changeRoomId.Load() && msg.Event.Invite.RoomId == remoteRoomId { + msg.Event.Invite.RoomId = roomId + } + case "disinvite": + if c.changeRoomId.Load() && msg.Event.Disinvite.RoomId == remoteRoomId { + msg.Event.Disinvite.RoomId = roomId + } + case "update": + if c.changeRoomId.Load() && msg.Event.Update.RoomId == remoteRoomId { + msg.Event.Update.RoomId = roomId + } + } + } + case "error": + if c.changeRoomId.Load() && msg.Error.Code == "already_joined" { + if len(msg.Error.Details) > 0 { + var details RoomErrorDetails + if err := json.Unmarshal(msg.Error.Details, &details); err == nil && details.Room != nil { + if details.Room.RoomId == remoteRoomId { + details.Room.RoomId = roomId + if data, err := json.Marshal(details); err == nil { + msg.Error.Details = data + } + } + } + } + } + case "room": + if message := c.message.Load(); message != nil { + if msg.Id != "" && message.Id == msg.Id { + // Got response to initial join request, clear id so future join + // requests will not be mapped to any client callbacks. + message.Id = "" + c.message.Store(message) + } + + room := message.Room + roomId = room.RoomId + remoteRoomId = room.Federation.RoomId + if remoteRoomId == "" { + remoteRoomId = room.RoomId + } + + c.roomId.Store(room.RoomId) + c.remoteRoomId.Store(remoteRoomId) + c.changeRoomId.Store(room.RoomId != remoteRoomId) + c.federation.Store(room.Federation) + } + + if msg.Room.RoomId == "" && c.closeOnLeave.Load() { + doClose = true + } else if c.changeRoomId.Load() && msg.Room.RoomId == remoteRoomId { + msg.Room.RoomId = roomId + } + case "message": + c.updateSessionRecipient(msg.Message.Recipient, localSessionId, remoteSessionId) + c.updateSessionSender(msg.Message.Sender, localSessionId, remoteSessionId) + if remoteSessionId != "" && len(msg.Message.Data) > 0 { + var ao AnswerOfferMessage + if json.Unmarshal(msg.Message.Data, &ao) == nil && (ao.Type == "offer" || ao.Type == "answer") { + changed := false + if ao.From == remoteSessionId { + ao.From = localSessionId + changed = true + } + if ao.To == remoteSessionId { + ao.To = localSessionId + changed = true + } + + if changed { + if data, err := json.Marshal(ao); err == nil { + msg.Message.Data = data + } + } + } + } + } + c.session.SendMessage(msg) + + if doClose { + c.Close() + } +} + +func (c *FederationClient) ProxyMessage(message *ClientMessage) error { + switch message.Type { + case "message": + if hello := c.hello.Load(); hello != nil { + c.updateSessionRecipient(&message.Message.Recipient, hello.SessionId, c.session.PublicId()) + } + } + + return c.SendMessage(message) +} + +func (c *FederationClient) SendMessage(message *ClientMessage) error { + c.mu.Lock() + defer c.mu.Unlock() + + return c.sendMessageLocked(message) +} + +func (c *FederationClient) deferMessage(message *ClientMessage) { + c.helloMu.Lock() + defer c.helloMu.Unlock() + if c.resumeId == "" { + return + } + + c.pendingMessages = append(c.pendingMessages, message) + if len(c.pendingMessages) >= warnPendingMessagesCount { + log.Printf("Session %s has %d pending federated messages", c.session.PublicId(), len(c.pendingMessages)) + } +} + +func (c *FederationClient) sendMessageLocked(message *ClientMessage) error { + if c.conn == nil { + if message.Type != "room" { + // Join requests will be automatically sent after the hello response has + // been received. + c.deferMessage(message) + } + return nil + } + + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) // nolint + writer, err := c.conn.NextWriter(websocket.TextMessage) + if err == nil { + if m, ok := (interface{}(message)).(easyjson.Marshaler); ok { + _, err = easyjson.MarshalToWriter(m, writer) + } else { + err = json.NewEncoder(writer).Encode(message) + } + } + if err == nil { + err = writer.Close() + } + if err != nil { + if err == websocket.ErrCloseSent { + // Already sent a "close", won't be able to send anything else. + return err + } + + log.Printf("Could not send message %+v for %s to federated client %s: %v", message, c.session.PublicId(), c.URL(), err) + c.deferMessage(message) + c.scheduleReconnectLocked() + } + + return nil +} diff --git a/federation_test.go b/federation_test.go new file mode 100644 index 00000000..a56edae2 --- /dev/null +++ b/federation_test.go @@ -0,0 +1,992 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2024 struktur AG + * + * @author Joachim Bauch + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package signaling + +import ( + "context" + "encoding/json" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_FederationInvalidToken(t *testing.T) { + CatchLogForTest(t) + + assert := assert.New(t) + require := require.New(t) + + _, hub2, server1, server2 := CreateClusteredHubsForTest(t) + + client := NewTestClient(t, server2, hub2) + defer client.CloseWithBye() + require.NoError(client.SendHelloV2(testDefaultUserId + "2")) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + _, err := client.RunUntilHello(ctx) + require.NoError(err) + + msg := &ClientMessage{ + Id: "join-room-fed", + Type: "room", + Room: &RoomClientMessage{ + RoomId: "test-room", + SessionId: "room-session-id", + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + Token: "invalid-token", + }, + }, + } + require.NoError(client.WriteJSON(msg)) + + if message, err := client.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg.Id, message.Id) + require.Equal("error", message.Type) + require.Equal("invalid_token", message.Error.Code) + } +} + +func Test_Federation(t *testing.T) { + CatchLogForTest(t) + + assert := assert.New(t) + require := require.New(t) + + hub1, hub2, server1, server2 := CreateClusteredHubsForTest(t) + + client1 := NewTestClient(t, server1, hub1) + defer client1.CloseWithBye() + require.NoError(client1.SendHelloV2(testDefaultUserId + "1")) + + client2 := NewTestClient(t, server2, hub2) + defer client2.CloseWithBye() + require.NoError(client2.SendHelloV2(testDefaultUserId + "2")) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + roomId := "test-room" + federatedRoomId := roomId + "@federated" + room1, err := client1.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, room1.Room.RoomId) + + assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + + now := time.Now() + token, err := client1.CreateHelloV2Token(testDefaultUserId+"2", now, now.Add(time.Minute)) + require.NoError(err) + + msg := &ClientMessage{ + Id: "join-room-fed", + Type: "room", + Room: &RoomClientMessage{ + RoomId: federatedRoomId, + SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + RoomId: roomId, + Token: token, + }, + }, + } + require.NoError(client2.WriteJSON(msg)) + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg.Id, message.Id) + require.Equal("room", message.Type) + require.Equal(federatedRoomId, message.Room.RoomId) + } + + // The client1 will see the remote session id for client2. + var remoteSessionId string + if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + assert.NoError(client1.checkSingleMessageJoined(message)) + evt := message.Event.Join[0] + remoteSessionId = evt.SessionId + assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) + assert.Equal(testDefaultUserId+"2", evt.UserId) + assert.True(evt.Federated) + } + + // The client2 will see its own session id, not the one from the remote server. + assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + + // Leaving and re-joining a room as "direct" session will trigger correct events. + if room, err := client1.JoinRoom(ctx, ""); assert.NoError(err) { + assert.Equal("", room.Room.RoomId) + } + + assert.NoError(client2.RunUntilLeft(ctx, hello1.Hello)) + + if room, err := client1.JoinRoom(ctx, roomId); assert.NoError(err) { + assert.Equal(roomId, room.Room.RoomId) + } + + assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ + SessionId: remoteSessionId, + UserId: hello2.Hello.UserId, + })) + assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello)) + + // Leaving and re-joining a room as "federated" session will trigger correct events. + if room, err := client2.JoinRoom(ctx, ""); assert.NoError(err) { + assert.Equal("", room.Room.RoomId) + } + + assert.NoError(client1.RunUntilLeft(ctx, &HelloServerMessage{ + SessionId: remoteSessionId, + UserId: hello2.Hello.UserId, + })) + + require.NoError(client2.WriteJSON(msg)) + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg.Id, message.Id) + require.Equal("room", message.Type) + require.Equal(federatedRoomId, message.Room.RoomId) + } + + // Client1 will receive the updated "remoteSessionId" + if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + assert.NoError(client1.checkSingleMessageJoined(message)) + evt := message.Event.Join[0] + remoteSessionId = evt.SessionId + assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) + assert.Equal(testDefaultUserId+"2", evt.UserId) + assert.True(evt.Federated) + } + assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + + // Test sending messages between sessions. + data1 := "from-1-to-2" + data2 := "from-2-to-1" + if assert.NoError(client1.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: remoteSessionId, + }, data1)) { + var payload string + if assert.NoError(checkReceiveClientMessage(ctx, client2, "session", hello1.Hello, &payload)) { + assert.Equal(data1, payload) + } + } + + if assert.NoError(client1.SendControl(MessageClientMessageRecipient{ + Type: "session", + SessionId: remoteSessionId, + }, data1)) { + var payload string + if assert.NoError(checkReceiveClientControl(ctx, client2, "session", hello1.Hello, &payload)) { + assert.Equal(data1, payload) + } + } + + if assert.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, data2)) { + var payload string + if assert.NoError(checkReceiveClientMessage(ctx, client1, "session", &HelloServerMessage{ + SessionId: remoteSessionId, + UserId: testDefaultUserId + "2", + }, &payload)) { + assert.Equal(data2, payload) + } + } + + if assert.NoError(client2.SendControl(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, data2)) { + var payload string + if assert.NoError(checkReceiveClientControl(ctx, client1, "session", &HelloServerMessage{ + SessionId: remoteSessionId, + UserId: testDefaultUserId + "2", + }, &payload)) { + assert.Equal(data2, payload) + } + } + + // Special handling for the "forceMute" control event. + forceMute := map[string]any{ + "action": "forceMute", + "peerId": remoteSessionId, + } + if assert.NoError(client1.SendControl(MessageClientMessageRecipient{ + Type: "session", + SessionId: remoteSessionId, + }, forceMute)) { + var payload map[string]any + if assert.NoError(checkReceiveClientControl(ctx, client2, "session", hello1.Hello, &payload)) { + // The sessionId in "peerId" will be replaced with the local one. + forceMute["peerId"] = hello2.Hello.SessionId + assert.Equal(forceMute, payload) + } + } + + data3 := "from-2-to-2" + // Clients can't send to their own (local) session id. + if assert.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello2.Hello.SessionId, + }, data3)) { + ctx2, cancel2 := context.WithTimeout(ctx, 200*time.Millisecond) + defer cancel2() + + if message, err := client2.RunUntilMessage(ctx2); err != nil && err != ErrNoMessageReceived && err != context.DeadlineExceeded { + t.Error(err) + } else { + assert.Nil(message) + } + } + + // Clients can't send to their own (remote) session id. + if assert.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: remoteSessionId, + }, data3)) { + ctx2, cancel2 := context.WithTimeout(ctx, 200*time.Millisecond) + defer cancel2() + + if message, err := client2.RunUntilMessage(ctx2); err != nil && err != ErrNoMessageReceived && err != context.DeadlineExceeded { + t.Error(err) + } else { + assert.Nil(message) + } + } + + // Simulate request from the backend that a federated user joined the call. + users := []map[string]interface{}{ + { + "sessionId": remoteSessionId, + "inCall": 1, + "actorId": "remoteUser@" + strings.TrimPrefix(server2.URL, "http://"), + "actorType": "federated_users", + }, + } + room := hub1.getRoom(roomId) + require.NotNil(room) + room.PublishUsersInCallChanged(users, users) + var event *EventServerMessage + // For the local user, it's a federated user on server 2 that joined. + assert.NoError(checkReceiveClientEvent(ctx, client1, "update", &event)) + assert.Equal(remoteSessionId, event.Update.Users[0]["sessionId"]) + assert.Equal("remoteUser@"+strings.TrimPrefix(server2.URL, "http://"), event.Update.Users[0]["actorId"]) + assert.Equal("federated_users", event.Update.Users[0]["actorType"]) + assert.Equal(roomId, event.Update.RoomId) + // For the federated user, it's a local user that joined. + assert.NoError(checkReceiveClientEvent(ctx, client2, "update", &event)) + assert.Equal(hello2.Hello.SessionId, event.Update.Users[0]["sessionId"]) + assert.Equal("remoteUser", event.Update.Users[0]["actorId"]) + assert.Equal("users", event.Update.Users[0]["actorType"]) + assert.Equal(federatedRoomId, event.Update.RoomId) + + // Simulate request from the backend that a local user joined the call. + users = []map[string]interface{}{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + "actorId": "localUser", + "actorType": "users", + }, + } + room.PublishUsersInCallChanged(users, users) + // For the local user, it's a local user that joined. + assert.NoError(checkReceiveClientEvent(ctx, client1, "update", &event)) + assert.Equal(hello1.Hello.SessionId, event.Update.Users[0]["sessionId"]) + assert.Equal("localUser", event.Update.Users[0]["actorId"]) + assert.Equal("users", event.Update.Users[0]["actorType"]) + assert.Equal(roomId, event.Update.RoomId) + // For the federated user, it's a federated user on server 1 that joined. + assert.NoError(checkReceiveClientEvent(ctx, client2, "update", &event)) + assert.Equal(hello1.Hello.SessionId, event.Update.Users[0]["sessionId"]) + assert.Equal("localUser@"+strings.TrimPrefix(server1.URL, "http://"), event.Update.Users[0]["actorId"]) + assert.Equal("federated_users", event.Update.Users[0]["actorType"]) + assert.Equal(federatedRoomId, event.Update.RoomId) + + // Joining another "direct" session will trigger correct events. + + client3 := NewTestClient(t, server1, hub1) + defer client3.CloseWithBye() + require.NoError(client3.SendHelloV2(testDefaultUserId + "3")) + + hello3, err := client3.RunUntilHello(ctx) + require.NoError(err) + + if room, err := client3.JoinRoom(ctx, roomId); assert.NoError(err) { + require.Equal(roomId, room.Room.RoomId) + } + + assert.NoError(client1.RunUntilJoined(ctx, hello3.Hello)) + assert.NoError(client2.RunUntilJoined(ctx, hello3.Hello)) + + assert.NoError(client3.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ + SessionId: remoteSessionId, + UserId: hello2.Hello.UserId, + }, hello3.Hello)) + + // Joining another "federated" session will trigger correct events. + + client4 := NewTestClient(t, server2, hub1) + defer client4.CloseWithBye() + require.NoError(client4.SendHelloV2(testDefaultUserId + "4")) + + hello4, err := client4.RunUntilHello(ctx) + require.NoError(err) + + token, err = client4.CreateHelloV2Token(testDefaultUserId+"4", now, now.Add(time.Minute)) + require.NoError(err) + + msg = &ClientMessage{ + Id: "join-room-fed", + Type: "room", + Room: &RoomClientMessage{ + RoomId: roomId, + SessionId: roomId + "-" + hello4.Hello.SessionId, + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + Token: token, + }, + }, + } + require.NoError(client4.WriteJSON(msg)) + + if message, err := client4.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg.Id, message.Id) + require.Equal("room", message.Type) + require.Equal(roomId, message.Room.RoomId) + } + + // The client1 will see the remote session id for client2. + var remoteSessionId4 string + if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + assert.NoError(client1.checkSingleMessageJoined(message)) + evt := message.Event.Join[0] + remoteSessionId4 = evt.SessionId + assert.NotEqual(hello4.Hello.SessionId, remoteSessionId) + assert.Equal(testDefaultUserId+"4", evt.UserId) + assert.True(evt.Federated) + } + + assert.NoError(client2.RunUntilJoined(ctx, &HelloServerMessage{ + SessionId: remoteSessionId4, + UserId: hello4.Hello.UserId, + })) + + assert.NoError(client3.RunUntilJoined(ctx, &HelloServerMessage{ + SessionId: remoteSessionId4, + UserId: hello4.Hello.UserId, + })) + + assert.NoError(client4.RunUntilJoined(ctx, hello1.Hello, &HelloServerMessage{ + SessionId: remoteSessionId, + UserId: hello2.Hello.UserId, + }, hello3.Hello, hello4.Hello)) + + room3, err := client2.JoinRoom(ctx, "") + if assert.NoError(err) { + assert.Equal("", room3.Room.RoomId) + } +} + +func Test_FederationJoinRoomTwice(t *testing.T) { + CatchLogForTest(t) + + assert := assert.New(t) + require := require.New(t) + + hub1, hub2, server1, server2 := CreateClusteredHubsForTest(t) + + client1 := NewTestClient(t, server1, hub1) + defer client1.CloseWithBye() + require.NoError(client1.SendHelloV2(testDefaultUserId + "1")) + + client2 := NewTestClient(t, server2, hub2) + defer client2.CloseWithBye() + require.NoError(client2.SendHelloV2(testDefaultUserId + "2")) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + roomId := "test-room" + federatedRoomId := roomId + "@federated" + room1, err := client1.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, room1.Room.RoomId) + + assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + + now := time.Now() + token, err := client1.CreateHelloV2Token(testDefaultUserId+"2", now, now.Add(time.Minute)) + require.NoError(err) + + msg := &ClientMessage{ + Id: "join-room-fed", + Type: "room", + Room: &RoomClientMessage{ + RoomId: federatedRoomId, + SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + RoomId: roomId, + Token: token, + }, + }, + } + require.NoError(client2.WriteJSON(msg)) + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg.Id, message.Id) + require.Equal("room", message.Type) + require.Equal(federatedRoomId, message.Room.RoomId) + } + + // The client1 will see the remote session id for client2. + var remoteSessionId string + if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + assert.NoError(client1.checkSingleMessageJoined(message)) + evt := message.Event.Join[0] + remoteSessionId = evt.SessionId + assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) + assert.Equal(hello2.Hello.UserId, evt.UserId) + assert.True(evt.Federated) + } + + // The client2 will see its own session id, not the one from the remote server. + assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + + msg2 := &ClientMessage{ + Id: "join-room-fed-2", + Type: "room", + Room: &RoomClientMessage{ + RoomId: federatedRoomId, + SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + RoomId: roomId, + Token: token, + }, + }, + } + require.NoError(client2.WriteJSON(msg2)) + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg2.Id, message.Id) + if assert.Equal("error", message.Type) { + assert.Equal("already_joined", message.Error.Code) + } + if assert.NotNil(message.Error.Details) { + var roomMsg RoomErrorDetails + if assert.NoError(json.Unmarshal(message.Error.Details, &roomMsg)) { + if assert.NotNil(roomMsg.Room) { + assert.Equal(federatedRoomId, roomMsg.Room.RoomId) + assert.Equal(string(testRoomProperties), string(roomMsg.Room.Properties)) + } + } + } + } +} + +func Test_FederationChangeRoom(t *testing.T) { + CatchLogForTest(t) + + assert := assert.New(t) + require := require.New(t) + + hub1, hub2, server1, server2 := CreateClusteredHubsForTest(t) + + client1 := NewTestClient(t, server1, hub1) + defer client1.CloseWithBye() + require.NoError(client1.SendHelloV2(testDefaultUserId + "1")) + + client2 := NewTestClient(t, server2, hub2) + defer client2.CloseWithBye() + require.NoError(client2.SendHelloV2(testDefaultUserId + "2")) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + roomId := "test-room" + federatedRoomId := roomId + "@federated" + room1, err := client1.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, room1.Room.RoomId) + + assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + + now := time.Now() + token, err := client1.CreateHelloV2Token(testDefaultUserId+"2", now, now.Add(time.Minute)) + require.NoError(err) + + msg := &ClientMessage{ + Id: "join-room-fed", + Type: "room", + Room: &RoomClientMessage{ + RoomId: federatedRoomId, + SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + RoomId: roomId, + Token: token, + }, + }, + } + require.NoError(client2.WriteJSON(msg)) + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg.Id, message.Id) + require.Equal("room", message.Type) + require.Equal(federatedRoomId, message.Room.RoomId) + } + + session2 := hub2.GetSessionByPublicId(hello2.Hello.SessionId).(*ClientSession) + fed := session2.GetFederationClient() + require.NotNil(fed) + localAddr := fed.conn.LocalAddr() + + // The client1 will see the remote session id for client2. + var remoteSessionId string + if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + assert.NoError(client1.checkSingleMessageJoined(message)) + evt := message.Event.Join[0] + remoteSessionId = evt.SessionId + assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) + assert.Equal(hello2.Hello.UserId, evt.UserId) + assert.True(evt.Federated) + } + + // The client2 will see its own session id, not the one from the remote server. + assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + + roomId2 := roomId + "-2" + federatedRoomId2 := roomId2 + "@federated" + msg2 := &ClientMessage{ + Id: "join-room-fed-2", + Type: "room", + Room: &RoomClientMessage{ + RoomId: federatedRoomId2, + SessionId: federatedRoomId2 + "-" + hello2.Hello.SessionId, + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + RoomId: roomId2, + Token: token, + }, + }, + } + require.NoError(client2.WriteJSON(msg2)) + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg2.Id, message.Id) + require.Equal("room", message.Type) + require.Equal(federatedRoomId2, message.Room.RoomId) + } + + fed2 := session2.GetFederationClient() + require.NotNil(fed2) + localAddr2 := fed2.conn.LocalAddr() + assert.Equal(localAddr, localAddr2) +} + +func Test_FederationMedia(t *testing.T) { + CatchLogForTest(t) + + assert := assert.New(t) + require := require.New(t) + + hub1, hub2, server1, server2 := CreateClusteredHubsForTest(t) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + mcu1, err := NewTestMCU() + require.NoError(err) + require.NoError(mcu1.Start(ctx)) + defer mcu1.Stop() + + hub1.SetMcu(mcu1) + + mcu2, err := NewTestMCU() + require.NoError(err) + require.NoError(mcu2.Start(ctx)) + defer mcu2.Stop() + + hub2.SetMcu(mcu2) + + client1 := NewTestClient(t, server1, hub1) + defer client1.CloseWithBye() + require.NoError(client1.SendHelloV2(testDefaultUserId + "1")) + + client2 := NewTestClient(t, server2, hub2) + defer client2.CloseWithBye() + require.NoError(client2.SendHelloV2(testDefaultUserId + "2")) + + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + roomId := "test-room" + room1, err := client1.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, room1.Room.RoomId) + + assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + + now := time.Now() + token, err := client1.CreateHelloV2Token(testDefaultUserId+"2", now, now.Add(time.Minute)) + require.NoError(err) + + msg := &ClientMessage{ + Id: "join-room-fed", + Type: "room", + Room: &RoomClientMessage{ + RoomId: roomId, + SessionId: roomId + "-" + hello2.Hello.SessionId, + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + Token: token, + }, + }, + } + require.NoError(client2.WriteJSON(msg)) + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg.Id, message.Id) + require.Equal("room", message.Type) + require.Equal(roomId, message.Room.RoomId) + } + + // The client1 will see the remote session id for client2. + var remoteSessionId string + if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + assert.NoError(client1.checkSingleMessageJoined(message)) + evt := message.Event.Join[0] + remoteSessionId = evt.SessionId + assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) + assert.Equal(testDefaultUserId+"2", evt.UserId) + assert.True(evt.Federated) + } + + // The client2 will see its own session id, not the one from the remote server. + assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + + require.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello2.Hello.SessionId, + }, MessageClientMessageData{ + Type: "offer", + Sid: "12345", + RoomType: "screen", + Payload: map[string]interface{}{ + "sdp": MockSdpOfferAudioAndVideo, + }, + })) + + require.NoError(client2.RunUntilAnswerFromSender(ctx, MockSdpAnswerAudioAndVideo, &MessageServerMessageSender{ + Type: "session", + SessionId: hello2.Hello.SessionId, + UserId: hello2.Hello.UserId, + })) +} + +func Test_FederationResume(t *testing.T) { + CatchLogForTest(t) + + assert := assert.New(t) + require := require.New(t) + + hub1, hub2, server1, server2 := CreateClusteredHubsForTest(t) + + client1 := NewTestClient(t, server1, hub1) + defer client1.CloseWithBye() + require.NoError(client1.SendHelloV2(testDefaultUserId + "1")) + + client2 := NewTestClient(t, server2, hub2) + defer client2.CloseWithBye() + require.NoError(client2.SendHelloV2(testDefaultUserId + "2")) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + roomId := "test-room" + federatedRoomId := roomId + "@federated" + room1, err := client1.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, room1.Room.RoomId) + + assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + + now := time.Now() + token, err := client1.CreateHelloV2Token(testDefaultUserId+"2", now, now.Add(time.Minute)) + require.NoError(err) + + msg := &ClientMessage{ + Id: "join-room-fed", + Type: "room", + Room: &RoomClientMessage{ + RoomId: federatedRoomId, + SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + RoomId: roomId, + Token: token, + }, + }, + } + require.NoError(client2.WriteJSON(msg)) + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg.Id, message.Id) + require.Equal("room", message.Type) + require.Equal(federatedRoomId, message.Room.RoomId) + } + + // The client1 will see the remote session id for client2. + var remoteSessionId string + if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + assert.NoError(client1.checkSingleMessageJoined(message)) + evt := message.Event.Join[0] + remoteSessionId = evt.SessionId + assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) + assert.Equal(testDefaultUserId+"2", evt.UserId) + assert.True(evt.Federated) + } + + // The client2 will see its own session id, not the one from the remote server. + assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + + session2 := hub2.GetSessionByPublicId(hello2.Hello.SessionId).(*ClientSession) + fed2 := session2.GetFederationClient() + require.NotNil(fed2) + fed2.mu.Lock() + err = fed2.conn.Close() + + data2 := "from-2-to-1" + assert.NoError(client2.SendMessage(MessageClientMessageRecipient{ + Type: "session", + SessionId: hello1.Hello.SessionId, + }, data2)) + fed2.mu.Unlock() + assert.NoError(err) + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("event", message.Type) + assert.Equal("room", message.Event.Target) + assert.Equal("federation_interrupted", message.Event.Type) + } + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("event", message.Type) + assert.Equal("room", message.Event.Target) + assert.Equal("federation_resumed", message.Event.Type) + assert.NotNil(message.Event.Resumed) + assert.True(*message.Event.Resumed) + } + + ctx1, cancel1 := context.WithTimeout(context.Background(), 200*time.Millisecond) + defer cancel1() + + var payload string + if assert.NoError(checkReceiveClientMessage(ctx, client1, "session", &HelloServerMessage{ + SessionId: remoteSessionId, + UserId: testDefaultUserId + "2", + }, &payload)) { + assert.Equal(data2, payload) + } + + if message, err := client1.RunUntilMessage(ctx1); err != nil && err != ErrNoMessageReceived && err != context.DeadlineExceeded { + assert.NoError(err) + } else { + assert.Nil(message) + } + + ctx2, cancel2 := context.WithTimeout(context.Background(), 200*time.Millisecond) + defer cancel2() + + if message, err := client2.RunUntilMessage(ctx2); err != nil && err != ErrNoMessageReceived && err != context.DeadlineExceeded { + assert.NoError(err) + } else { + assert.Nil(message) + } +} + +func Test_FederationResumeNewSession(t *testing.T) { + CatchLogForTest(t) + + assert := assert.New(t) + require := require.New(t) + + hub1, hub2, server1, server2 := CreateClusteredHubsForTest(t) + + client1 := NewTestClient(t, server1, hub1) + defer client1.CloseWithBye() + require.NoError(client1.SendHelloV2(testDefaultUserId + "1")) + + client2 := NewTestClient(t, server2, hub2) + defer client2.CloseWithBye() + require.NoError(client2.SendHelloV2(testDefaultUserId + "2")) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + hello1, err := client1.RunUntilHello(ctx) + require.NoError(err) + + hello2, err := client2.RunUntilHello(ctx) + require.NoError(err) + + roomId := "test-room" + federatedRoomId := roomId + "@federated" + room1, err := client1.JoinRoom(ctx, roomId) + require.NoError(err) + require.Equal(roomId, room1.Room.RoomId) + + assert.NoError(client1.RunUntilJoined(ctx, hello1.Hello)) + + now := time.Now() + token, err := client1.CreateHelloV2Token(testDefaultUserId+"2", now, now.Add(time.Minute)) + require.NoError(err) + + msg := &ClientMessage{ + Id: "join-room-fed", + Type: "room", + Room: &RoomClientMessage{ + RoomId: federatedRoomId, + SessionId: federatedRoomId + "-" + hello2.Hello.SessionId, + Federation: &RoomFederationMessage{ + SignalingUrl: server1.URL, + NextcloudUrl: server1.URL, + RoomId: roomId, + Token: token, + }, + }, + } + require.NoError(client2.WriteJSON(msg)) + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal(msg.Id, message.Id) + require.Equal("room", message.Type) + require.Equal(federatedRoomId, message.Room.RoomId) + } + + // The client1 will see the remote session id for client2. + var remoteSessionId string + if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + assert.NoError(client1.checkSingleMessageJoined(message)) + evt := message.Event.Join[0] + remoteSessionId = evt.SessionId + assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) + assert.Equal(hello2.Hello.UserId, evt.UserId) + assert.True(evt.Federated) + } + + // The client2 will see its own session id, not the one from the remote server. + assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) + + remoteSession2 := hub1.GetSessionByPublicId(remoteSessionId).(*ClientSession) + // Simulate disconnected federated client with an expired session. + if client := remoteSession2.GetClient(); client != nil { + remoteSession2.ClearClient(client) + client.Close() + } + remoteSession2.Close() + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("event", message.Type) + assert.Equal("room", message.Event.Target) + assert.Equal("federation_interrupted", message.Event.Type) + } + + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("event", message.Type) + assert.Equal("room", message.Event.Target) + assert.Equal("federation_resumed", message.Event.Type) + assert.NotNil(message.Event.Resumed) + assert.False(*message.Event.Resumed) + } + + // Client1 will get a "leave" for the expired session and a "join" with the + // new remote session id. + assert.NoError(client1.RunUntilLeft(ctx, &HelloServerMessage{ + SessionId: remoteSessionId, + UserId: hello2.Hello.UserId, + })) + if message, err := client1.RunUntilMessage(ctx); assert.NoError(err) { + assert.NoError(client1.checkSingleMessageJoined(message)) + evt := message.Event.Join[0] + assert.NotEqual(remoteSessionId, evt.SessionId) + assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) + remoteSessionId = evt.SessionId + assert.NotEqual(hello2.Hello.SessionId, remoteSessionId) + assert.Equal(hello2.Hello.UserId, evt.UserId) + assert.True(evt.Federated) + } + + // client2 will join the room again after the reconnect with the new + // session and get "joined" events for all sessions in the room (including + // its own). + if message, err := client2.RunUntilMessage(ctx); assert.NoError(err) { + assert.Equal("", message.Id) + require.Equal("room", message.Type) + require.Equal(federatedRoomId, message.Room.RoomId) + } + assert.NoError(client2.RunUntilJoined(ctx, hello1.Hello, hello2.Hello)) +} diff --git a/go.mod b/go.mod index 7b896a9b..a4059432 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/oschwald/maxminddb-golang v1.13.1 github.com/pion/sdp/v3 v3.0.9 github.com/prometheus/client_golang v1.19.1 + github.com/stretchr/testify v1.9.0 go.etcd.io/etcd/api/v3 v3.5.15 go.etcd.io/etcd/client/pkg/v3 v3.5.15 go.etcd.io/etcd/client/v3 v3.5.15 @@ -56,6 +57,7 @@ require ( github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pion/randutil v0.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect @@ -87,5 +89,6 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/hub.go b/hub.go index 42d9c537..a2ec81e1 100644 --- a/hub.go +++ b/hub.go @@ -27,6 +27,7 @@ import ( "crypto/ed25519" "crypto/hmac" "crypto/sha256" + "crypto/tls" "crypto/x509" "encoding/base64" "encoding/hex" @@ -75,6 +76,9 @@ var ( // MCU requests will be cancelled if they take too long. defaultMcuTimeoutSeconds = 10 + // Federation requests will be cancelled if they take too long. + defaultFederationTimeoutSeconds = 10 + // New connections have to send a "Hello" request after 2 seconds. initialHelloTimeout = 2 * time.Second @@ -178,6 +182,9 @@ type Hub struct { rpcClients *GrpcClients throttler Throttler + + skipFederationVerify bool + federationTimeout time.Duration } func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer, rpcClients *GrpcClients, etcdClient *EtcdClient, r *mux.Router, version string) (*Hub, error) { @@ -241,6 +248,16 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer return nil, err } + skipFederationVerify, _ := config.GetBool("federation", "skipverify") + if skipFederationVerify { + log.Println("WARNING: Federation target verification is disabled!") + } + federationTimeoutSeconds, _ := config.GetInt("federation", "timeout") + if federationTimeoutSeconds <= 0 { + federationTimeoutSeconds = defaultFederationTimeoutSeconds + } + federationTimeout := time.Duration(federationTimeoutSeconds) * time.Second + if !trustedProxiesIps.Empty() { log.Printf("Trusted proxies: %s", trustedProxiesIps) } else { @@ -349,6 +366,9 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer rpcClients: rpcClients, throttler: throttler, + + skipFederationVerify: skipFederationVerify, + federationTimeout: federationTimeout, } hub.trustedProxies.Store(trustedProxiesIps) if len(geoipOverrides) > 0 { @@ -1005,6 +1025,18 @@ func (h *Hub) processMessage(client HandlerClient, data []byte) { return } + isLocalMessage := message.Type == "room" || + message.Type == "hello" || + message.Type == "bye" + if cs, ok := session.(*ClientSession); ok && !isLocalMessage { + if federated := cs.GetFederationClient(); federated != nil { + if err := federated.ProxyMessage(&message); err != nil { + client.SendMessage(message.NewWrappedErrorServerMessage(err)) + } + return + } + } + switch message.Type { case "room": h.processRoom(session, &message) @@ -1202,6 +1234,8 @@ func (h *Hub) processHello(client HandlerClient, message *ClientMessage) { switch message.Hello.Auth.Type { case HelloClientTypeClient: + fallthrough + case HelloClientTypeFederation: h.processHelloClient(client, message) case HelloClientTypeInternal: h.processHelloInternal(client, message) @@ -1240,7 +1274,23 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message return nil, nil, InvalidBackendUrl } - token, err := jwt.ParseWithClaims(message.Hello.Auth.helloV2Params.Token, &HelloV2TokenClaims{}, func(token *jwt.Token) (interface{}, error) { + var tokenString string + var tokenClaims jwt.Claims + switch message.Hello.Auth.Type { + case HelloClientTypeClient: + tokenString = message.Hello.Auth.helloV2Params.Token + tokenClaims = &HelloV2TokenClaims{} + case HelloClientTypeFederation: + if !h.backend.capabilities.HasCapabilityFeature(ctx, url, FeatureFederationV2) { + return nil, nil, ErrFederationNotSupported + } + + tokenString = message.Hello.Auth.federationParams.Token + tokenClaims = &FederationTokenClaims{} + default: + return nil, nil, InvalidClientType + } + token, err := jwt.ParseWithClaims(tokenString, tokenClaims, func(token *jwt.Token) (interface{}, error) { // Only public-private-key algorithms are supported. var loadKeyFunc func([]byte) (interface{}, error) switch token.Method.(type) { @@ -1316,15 +1366,26 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message return nil, nil, InvalidToken } - claims, ok := token.Claims.(*HelloV2TokenClaims) - if !ok || !token.Valid { - return nil, nil, InvalidToken + var authTokenClaims AuthTokenClaims + switch message.Hello.Auth.Type { + case HelloClientTypeClient: + claims, ok := token.Claims.(*HelloV2TokenClaims) + if !ok || !token.Valid { + return nil, nil, InvalidToken + } + authTokenClaims = claims + case HelloClientTypeFederation: + claims, ok := token.Claims.(*FederationTokenClaims) + if !ok || !token.Valid { + return nil, nil, InvalidToken + } + authTokenClaims = claims } now := time.Now() - if !claims.VerifyIssuedAt(now, true) { + if !authTokenClaims.VerifyIssuedAt(now, true) { return nil, nil, TokenNotValidYet } - if !claims.VerifyExpiresAt(now, true) { + if !authTokenClaims.VerifyExpiresAt(now, true) { return nil, nil, TokenExpired } @@ -1332,8 +1393,8 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message Type: "auth", Auth: &BackendClientAuthResponse{ Version: message.Hello.Version, - UserId: claims.Subject, - User: claims.UserData, + UserId: authTokenClaims.TokenSubject(), + User: authTokenClaims.TokenUserData(), }, } return backend, auth, nil @@ -1481,7 +1542,7 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { roomId := message.Room.RoomId if roomId == "" { // We can handle leaving a room directly. - if session.LeaveRoom(true) != nil { + if session.LeaveRoomWithMessage(true, message) != nil { // User was in a room before, so need to notify about leaving it. h.sendRoom(session, message, nil) if session.UserId() == "" && session.ClientType() != HelloClientTypeInternal { @@ -1492,6 +1553,82 @@ func (h *Hub) processRoom(sess Session, message *ClientMessage) { return } + if federation := message.Room.Federation; federation != nil { + h.mu.Lock() + // The session will join a room, make sure it doesn't expire while connecting. + delete(h.anonymousSessions, session) + h.mu.Unlock() + + ctx, cancel := context.WithTimeout(session.Context(), h.federationTimeout) + defer cancel() + + client := session.GetFederationClient() + var err error + if client != nil { + if client.CanReuse(federation) { + err = client.ChangeRoom(message) + if errors.Is(err, ErrNotConnected) { + client = nil + } + } else { + client = nil + } + } + if client == nil { + client, err = NewFederationClient(ctx, h, session, message) + } + + if err != nil { + if session.UserId() == "" && client == nil { + h.startWaitAnonymousSessionRoom(session) + } + var ae *Error + if errors.As(err, &ae) { + session.SendMessage(message.NewErrorServerMessage(ae)) + return + } + + var details interface{} + var ce *tls.CertificateVerificationError + if errors.As(err, &ce) { + details = map[string]string{ + "code": "certificate_verification_error", + "message": ce.Error(), + } + } + var ne net.Error + if details == nil && errors.As(err, &ne) { + details = map[string]string{ + "code": "network_error", + "message": ne.Error(), + } + } + if details == nil { + var we websocket.HandshakeError + if errors.Is(err, websocket.ErrBadHandshake) { + details = map[string]string{ + "code": "network_error", + "message": err.Error(), + } + } else if errors.As(err, &we) { + details = map[string]string{ + "code": "network_error", + "message": we.Error(), + } + } + } + + log.Printf("Error creating federation client to %s for %s to join room %s: %s", federation.SignalingUrl, session.PublicId(), roomId, err) + session.SendMessage(message.NewErrorServerMessage( + NewErrorDetail("federation_error", "Failed to create federation client.", details), + )) + return + } + + session.SetFederationClient(client) + return + } + if room := h.getRoomForBackend(roomId, session.Backend()); room != nil && room.HasSession(session) { // Session already is in that room, no action needed. roomSessionId := message.Room.SessionId diff --git a/hub_test.go b/hub_test.go index 70f6f290..c8c7b37b 100644 --- a/hub_test.go +++ b/hub_test.go @@ -196,11 +196,17 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http server2.Close() }) - nats := startLocalNatsServer(t) + nats1 := startLocalNatsServer(t) + var nats2 string + if strings.Contains(t.Name(), "Federation") { + nats2 = startLocalNatsServer(t) + } else { + nats2 = nats1 + } grpcServer1, addr1 := NewGrpcServerForTest(t) grpcServer2, addr2 := NewGrpcServerForTest(t) - events1, err := NewAsyncEvents(nats) + events1, err := NewAsyncEvents(nats1) if err != nil { t.Fatal(err) } @@ -220,7 +226,7 @@ func CreateClusteredHubsForTestWithConfig(t *testing.T, getConfigFunc func(*http if err != nil { t.Fatal(err) } - events2, err := NewAsyncEvents(nats) + events2, err := NewAsyncEvents(nats2) if err != nil { t.Fatal(err) } @@ -699,6 +705,9 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { if strings.Contains(t.Name(), "V3Api") { features = append(features, "signaling-v3") } + if strings.Contains(t.Name(), "Federation") { + features = append(features, "federation-v2") + } signaling := map[string]interface{}{ "foo": "bar", "baz": 42, @@ -713,7 +722,7 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { if os.Getenv("SKIP_V2_CAPABILITIES") != "" { useV2 = false } - if strings.Contains(t.Name(), "V2") && useV2 { + if (strings.Contains(t.Name(), "V2") && useV2) || strings.Contains(t.Name(), "Federation") { key := getPublicAuthToken(t) public, err := x509.MarshalPKIXPublicKey(key) if err != nil { diff --git a/room.go b/room.go index a84230c7..2ef1f890 100644 --- a/room.go +++ b/room.go @@ -367,6 +367,7 @@ func (r *Room) notifySessionJoined(sessionId string) { } if s, ok := s.(*ClientSession); ok { entry.RoomSessionId = s.RoomSessionId() + entry.Federated = s.ClientType() == HelloClientTypeFederation } events = append(events, entry) } @@ -535,6 +536,7 @@ func (r *Room) PublishSessionJoined(session Session, sessionData *RoomSessionDat } if session, ok := session.(*ClientSession); ok { message.Event.Join[0].RoomSessionId = session.RoomSessionId() + message.Event.Join[0].Federated = session.ClientType() == HelloClientTypeFederation } if err := r.publish(message); err != nil { log.Printf("Could not publish session joined message in room %s: %s", r.Id(), err) @@ -709,7 +711,8 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) { continue } - if session.ClientType() == HelloClientTypeInternal { + if session.ClientType() == HelloClientTypeInternal || + session.ClientType() == HelloClientTypeFederation { continue } diff --git a/server.conf.in b/server.conf.in index b748cacd..85630d5a 100644 --- a/server.conf.in +++ b/server.conf.in @@ -55,6 +55,15 @@ blockkey = -encryption-key- # value as configured in the respective internal services. internalsecret = the-shared-secret-for-internal-clients +[federation] +# If set to "true", certificate validation of federation targets will be skipped. +# This should only be enabled during development, e.g. to work with self-signed +# certificates. +#skipverify = false + +# Timeout in seconds for requests to federation targets. +#timeout = 10 + [backend] # Type of backend configuration. # Defaults to "static". diff --git a/testclient_test.go b/testclient_test.go index 2dc6a83d..e65d1faa 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -401,14 +401,14 @@ func (c *TestClient) SendHelloV2(userid string) error { return c.SendHelloV2WithTimes(userid, now, now.Add(time.Minute)) } -func (c *TestClient) SendHelloV2WithTimes(userid string, issuedAt time.Time, expiresAt time.Time) error { +func (c *TestClient) CreateHelloV2Token(userid string, issuedAt time.Time, expiresAt time.Time) (string, error) { userdata := map[string]string{ "displayname": "Displayname " + userid, } data, err := json.Marshal(userdata) if err != nil { - c.t.Fatal(err) + return "", err } claims := &HelloV2TokenClaims{ @@ -434,7 +434,11 @@ func (c *TestClient) SendHelloV2WithTimes(userid string, issuedAt time.Time, exp token = jwt.NewWithClaims(jwt.SigningMethodRS256, claims) } private := getPrivateAuthToken(c.t) - tokenString, err := token.SignedString(private) + return token.SignedString(private) +} + +func (c *TestClient) SendHelloV2WithTimes(userid string, issuedAt time.Time, expiresAt time.Time) error { + tokenString, err := c.CreateHelloV2Token(userid, issuedAt, expiresAt) if err != nil { c.t.Fatal(err) } @@ -724,6 +728,9 @@ func (c *TestClient) JoinRoomWithRoomSession(ctx context.Context, roomId string, if err := checkMessageType(message, "room"); err != nil { return nil, err } + if message.Id != msg.Id { + return nil, fmt.Errorf("expected message id %s, got %s", msg.Id, message.Id) + } return message, nil } @@ -993,6 +1000,10 @@ func (c *TestClient) RunUntilOffer(ctx context.Context, offer string) error { } func (c *TestClient) RunUntilAnswer(ctx context.Context, answer string) error { + return c.RunUntilAnswerFromSender(ctx, answer, nil) +} + +func (c *TestClient) RunUntilAnswerFromSender(ctx context.Context, answer string, sender *MessageServerMessageSender) error { message, err := c.RunUntilMessage(ctx) if err != nil { return err @@ -1003,6 +1014,15 @@ func (c *TestClient) RunUntilAnswer(ctx context.Context, answer string) error { return err } + if sender != nil { + if err := checkMessageSender(c.hub, message.Message.Sender, sender.Type, &HelloServerMessage{ + SessionId: sender.SessionId, + UserId: sender.UserId, + }); err != nil { + return err + } + } + var data map[string]interface{} if err := json.Unmarshal(message.Message.Data, &data); err != nil { return err