From b7f8f839447a4602fcfaea7635f6e5a275bb370d Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 10 Dec 2025 21:07:32 +0100 Subject: [PATCH] Move Talk capabilities to talk package. --- .codecov.yml | 4 ++ Makefile | 2 +- api_backend.go | 18 ------- backend_client.go | 15 +++--- backend_client_test.go | 9 ++-- client/main.go | 7 +-- hub.go | 3 +- hub_test.go | 17 ++++--- room_ping.go | 5 +- capabilities.go => talk/capabilities.go | 6 ++- .../capabilities_test.go | 6 ++- talk/ocs.go | 50 +++++++++++++++++++ 12 files changed, 94 insertions(+), 48 deletions(-) rename capabilities.go => talk/capabilities.go (98%) rename capabilities_test.go => talk/capabilities_test.go (99%) create mode 100644 talk/ocs.go diff --git a/.codecov.yml b/.codecov.yml index f59167a..241b309 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -62,6 +62,10 @@ component_management: name: server paths: - server/** + - component_id: module_talk + name: talk + paths: + - talk/** - component_id: module_test name: test paths: diff --git a/Makefile b/Makefile index d511c32..a203c28 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ PROTO_FILES := $(filter-out $(GRPC_PROTO_FILES),$(basename $(wildcard *.proto))) PROTO_GO_FILES := $(addsuffix .pb.go,$(PROTO_FILES)) GRPC_PROTO_GO_FILES := $(addsuffix .pb.go,$(GRPC_PROTO_FILES)) $(addsuffix _grpc.pb.go,$(GRPC_PROTO_FILES)) TEST_GO_FILES := $(wildcard *_test.go)) -EASYJSON_FILES := $(filter-out $(TEST_GO_FILES),$(wildcard api*.go)) +EASYJSON_FILES := $(filter-out $(TEST_GO_FILES),$(wildcard api*.go talk/ocs.go)) EASYJSON_GO_FILES := $(patsubst %.go,%_easyjson.go,$(EASYJSON_FILES)) COMMON_GO_FILES := $(filter-out continentmap.go $(PROTO_GO_FILES) $(GRPC_PROTO_GO_FILES) $(EASYJSON_GO_FILES) $(TEST_GO_FILES),$(wildcard *.go)) CLIENT_TEST_GO_FILES := $(wildcard client/*_test.go)) diff --git a/api_backend.go b/api_backend.go index 28b6462..b295d5e 100644 --- a/api_backend.go +++ b/api_backend.go @@ -418,24 +418,6 @@ func NewBackendClientSessionRequest(roomid string, action string, sessionid Publ return request } -type OcsMeta struct { - Status string `json:"status"` - StatusCode int `json:"statuscode"` - Message string `json:"message"` -} - -type OcsBody struct { - Meta OcsMeta `json:"meta"` - Data json.RawMessage `json:"data"` -} - -type OcsResponse struct { - json.Marshaler - json.Unmarshaler - - Ocs *OcsBody `json:"ocs"` -} - // See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 type TurnCredentials struct { Username string `json:"username"` diff --git a/backend_client.go b/backend_client.go index 826e816..1032d49 100644 --- a/backend_client.go +++ b/backend_client.go @@ -35,6 +35,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/pool" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) var ( @@ -54,7 +55,7 @@ type BackendClient struct { backends *BackendConfiguration pool *pool.HttpClientPool - capabilities *Capabilities + capabilities *talk.Capabilities buffers pool.BufferPool } @@ -75,7 +76,7 @@ func NewBackendClient(ctx context.Context, config *goconf.ConfigFile, maxConcurr return nil, err } - capabilities, err := NewCapabilities(version, pool) + capabilities, err := talk.NewCapabilities(version, pool) if err != nil { return nil, err } @@ -113,10 +114,6 @@ func (b *BackendClient) IsUrlAllowed(u *url.URL) bool { return b.backends.IsUrlAllowed(u) } -func isOcsRequest(u *url.URL) bool { - return strings.Contains(u.Path, "/ocs/v2.php") || strings.Contains(u.Path, "/ocs/v1.php") -} - // PerformJSONRequest sends a JSON POST request to the given url and decodes // the result into "response". func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, request any, response any) error { @@ -131,7 +128,7 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ } var requestUrl *url.URL - if b.capabilities.HasCapabilityFeature(ctx, u, FeatureSignalingV3Api) { + if b.capabilities.HasCapabilityFeature(ctx, u, talk.FeatureSignalingV3Api) { newUrl := *u newUrl.Path = strings.ReplaceAll(newUrl.Path, "/spreed/api/v1/signaling/", "/spreed/api/v3/signaling/") newUrl.Path = strings.ReplaceAll(newUrl.Path, "/spreed/api/v2/signaling/", "/spreed/api/v3/signaling/") @@ -205,7 +202,7 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ defer b.buffers.Put(body) - if isOcsRequest(u) || req.Header.Get("OCS-APIRequest") != "" { + if talk.IsOcsRequest(u) || req.Header.Get("OCS-APIRequest") != "" { // OCS response are wrapped in an OCS container that needs to be parsed // to get the actual contents: // { @@ -214,7 +211,7 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ // "data": { ... } // } // } - var ocs OcsResponse + var ocs talk.OcsResponse if err := json.Unmarshal(body.Bytes(), &ocs); err != nil { logger.Printf("Could not decode OCS response %s from %s: %s", body.String(), req.URL, err) statsBackendClientError.WithLabelValues(backend.Id(), "error_decoding_ocs").Inc() diff --git a/backend_client_test.go b/backend_client_test.go index 3b60930..25625cb 100644 --- a/backend_client_test.go +++ b/backend_client_test.go @@ -37,12 +37,13 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/pool" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) func returnOCS(t *testing.T, w http.ResponseWriter, body []byte) { - response := OcsResponse{ - Ocs: &OcsBody{ - Meta: OcsMeta{ + response := talk.OcsResponse{ + Ocs: &talk.OcsBody{ + Meta: talk.OcsMeta{ Status: "OK", StatusCode: http.StatusOK, Message: "OK", @@ -51,7 +52,7 @@ func returnOCS(t *testing.T, w http.ResponseWriter, body []byte) { }, } if strings.Contains(t.Name(), "Throttled") { - response.Ocs.Meta = OcsMeta{ + response.Ocs.Meta = talk.OcsMeta{ Status: "failure", StatusCode: 429, Message: "Reached maximum delay", diff --git a/client/main.go b/client/main.go index bbfdd17..5d87f3f 100644 --- a/client/main.go +++ b/client/main.go @@ -49,6 +49,7 @@ import ( signaling "github.com/strukturag/nextcloud-spreed-signaling" "github.com/strukturag/nextcloud-spreed-signaling/internal" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) var ( @@ -458,9 +459,9 @@ func registerAuthHandler(router *mux.Router) { } rawdata := json.RawMessage(data) - payload := &signaling.OcsResponse{ - Ocs: &signaling.OcsBody{ - Meta: signaling.OcsMeta{ + payload := &talk.OcsResponse{ + Ocs: &talk.OcsBody{ + Meta: talk.OcsMeta{ Status: "ok", StatusCode: http.StatusOK, Message: http.StatusText(http.StatusOK), diff --git a/hub.go b/hub.go index 917546a..9a3783c 100644 --- a/hub.go +++ b/hub.go @@ -56,6 +56,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/container" "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) var ( @@ -1403,7 +1404,7 @@ func (h *Hub) processHelloV2(ctx context.Context, client HandlerClient, message tokenString = message.Hello.Auth.helloV2Params.Token tokenClaims = &HelloV2TokenClaims{} case HelloClientTypeFederation: - if !h.backend.capabilities.HasCapabilityFeature(ctx, url, FeatureFederationV2) { + if !h.backend.capabilities.HasCapabilityFeature(ctx, url, talk.FeatureFederationV2) { return nil, nil, ErrFederationNotSupported } diff --git a/hub_test.go b/hub_test.go index 2fc6f3a..1ea1fd7 100644 --- a/hub_test.go +++ b/hub_test.go @@ -58,6 +58,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" "github.com/strukturag/nextcloud-spreed-signaling/nats" + "github.com/strukturag/nextcloud-spreed-signaling/talk" "github.com/strukturag/nextcloud-spreed-signaling/test" ) @@ -374,9 +375,9 @@ func validateBackendChecksum(t *testing.T, f func(http.ResponseWriter, *http.Req assert.NoError(err) if r.Header.Get("OCS-APIRequest") != "" { - var ocs OcsResponse - ocs.Ocs = &OcsBody{ - Meta: OcsMeta{ + var ocs talk.OcsResponse + ocs.Ocs = &talk.OcsBody{ + Meta: talk.OcsMeta{ Status: "ok", StatusCode: http.StatusOK, Message: http.StatusText(http.StatusOK), @@ -778,8 +779,8 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { "config": config, }) assert.NoError(t, err) - response := &CapabilitiesResponse{ - Version: CapabilitiesVersion{ + response := &talk.CapabilitiesResponse{ + Version: talk.CapabilitiesVersion{ Major: 20, }, Capabilities: map[string]json.RawMessage{ @@ -790,9 +791,9 @@ func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) { data, err := json.Marshal(response) assert.NoError(t, err, "Could not marshal %+v", response) - var ocs OcsResponse - ocs.Ocs = &OcsBody{ - Meta: OcsMeta{ + var ocs talk.OcsResponse + ocs.Ocs = &talk.OcsBody{ + Meta: talk.OcsMeta{ Status: "ok", StatusCode: http.StatusOK, Message: http.StatusText(http.StatusOK), diff --git a/room_ping.go b/room_ping.go index 38b5930..1034c5d 100644 --- a/room_ping.go +++ b/room_ping.go @@ -30,6 +30,7 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/internal" "github.com/strukturag/nextcloud-spreed-signaling/log" + "github.com/strukturag/nextcloud-spreed-signaling/talk" ) type pingEntries struct { @@ -70,13 +71,13 @@ type RoomPing struct { closer *internal.Closer backend *BackendClient - capabilities *Capabilities + capabilities *talk.Capabilities // +checklocks:mu entries map[string]*pingEntries } -func NewRoomPing(backend *BackendClient, capabilities *Capabilities) (*RoomPing, error) { +func NewRoomPing(backend *BackendClient, capabilities *talk.Capabilities) (*RoomPing, error) { result := &RoomPing{ closer: internal.NewCloser(), backend: backend, diff --git a/capabilities.go b/talk/capabilities.go similarity index 98% rename from capabilities.go rename to talk/capabilities.go index 784c18e..d629d8f 100644 --- a/capabilities.go +++ b/talk/capabilities.go @@ -19,7 +19,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package talk import ( "context" @@ -59,6 +59,10 @@ const ( var ( ErrUnexpectedHttpStatus = errors.New("unexpected_http_status") // +checklocksignore: Global readonly variable. + + ErrUnsupportedContentType = errors.New("unsupported_content_type") // +checklocksignore: Global readonly variable. + + ErrIncompleteResponse = errors.New("incomplete OCS response") // +checklocksignore: Global readonly variable. ) type capabilitiesEntry struct { diff --git a/capabilities_test.go b/talk/capabilities_test.go similarity index 99% rename from capabilities_test.go rename to talk/capabilities_test.go index b3980c9..2f64aaa 100644 --- a/capabilities_test.go +++ b/talk/capabilities_test.go @@ -19,7 +19,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package signaling +package talk import ( "context" @@ -46,6 +46,10 @@ import ( "github.com/strukturag/nextcloud-spreed-signaling/pool" ) +const ( + testTimeout = 10 * time.Second +) + func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*CapabilitiesResponse, http.ResponseWriter) error) (*url.URL, *Capabilities) { require := require.New(t) assert := assert.New(t) diff --git a/talk/ocs.go b/talk/ocs.go new file mode 100644 index 0000000..32b933c --- /dev/null +++ b/talk/ocs.go @@ -0,0 +1,50 @@ +/** + * Standalone signaling server for the Nextcloud Spreed app. + * Copyright (C) 2025 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 talk + +import ( + "encoding/json" + "net/url" + "strings" +) + +type OcsMeta struct { + Status string `json:"status"` + StatusCode int `json:"statuscode"` + Message string `json:"message"` +} + +type OcsBody struct { + Meta OcsMeta `json:"meta"` + Data json.RawMessage `json:"data"` +} + +type OcsResponse struct { + json.Marshaler + json.Unmarshaler + + Ocs *OcsBody `json:"ocs"` +} + +func IsOcsRequest(u *url.URL) bool { + return strings.Contains(u.Path, "/ocs/v2.php") || strings.Contains(u.Path, "/ocs/v1.php") +}